@beyondwork/docx-react-component 1.0.12 → 1.0.13

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.
@@ -14,9 +14,14 @@ import type {
14
14
  CompatibilityReport,
15
15
  EditorError,
16
16
  EditorWarning,
17
+ FormattingAlignment,
17
18
  ExportDocxOptions,
19
+ InsertImageOptions,
20
+ InsertTableOptions,
18
21
  PersistedEditorSnapshot,
19
22
  RuntimeRenderSnapshot,
23
+ SearchOptions,
24
+ SearchResultSnapshot,
20
25
  SelectionSnapshot as PublicSelectionSnapshot,
21
26
  ExportResult,
22
27
  WordReviewEditorEvent,
@@ -27,8 +32,24 @@ import {
27
32
  createDetachedAnchor,
28
33
  createNodeAnchor,
29
34
  createRangeAnchor,
35
+ type TransactionMapping,
30
36
  } from "../core/selection/mapping.ts";
31
- import { createCanonicalDocumentId } from "../core/state/editor-state.ts";
37
+ import {
38
+ applyFormattingOperationToDocument,
39
+ getFormattingStateFromRenderSnapshot,
40
+ } from "../core/commands/formatting-commands.ts";
41
+ import { insertImage as insertImageInDocument } from "../core/commands/image-commands.ts";
42
+ import {
43
+ applyTableStructureOperation,
44
+ } from "../core/commands/table-structure-commands.ts";
45
+ import {
46
+ insertPageBreak as insertPageBreakInDocument,
47
+ insertTable as insertTableInDocument,
48
+ } from "../core/commands/text-commands.ts";
49
+ import {
50
+ createCanonicalDocumentId,
51
+ type SelectionSnapshot as InternalSelectionSnapshot,
52
+ } from "../core/state/editor-state.ts";
32
53
  import {
33
54
  createDocumentRuntime,
34
55
  type DocumentRuntime,
@@ -39,7 +60,14 @@ import {
39
60
  hasValidPersistedSourcePackageDigest,
40
61
  } from "../io/source-package-provenance.ts";
41
62
  import { deriveCapabilities } from "../runtime/session-capabilities";
42
- import { TwProseMirrorSurface } from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
63
+ import {
64
+ createSearchExcerpt,
65
+ findSearchMatches,
66
+ } from "../ui-tailwind/editor-surface/search-plugin";
67
+ import {
68
+ TwProseMirrorSurface,
69
+ type TwProseMirrorSurfaceRef,
70
+ } from "../ui-tailwind/editor-surface/tw-prosemirror-surface";
43
71
  import { TwReviewWorkspace } from "../ui-tailwind/tw-review-workspace";
44
72
  import type { ReviewRailTab } from "../ui-tailwind/review/tw-review-rail";
45
73
  import type { ViewMode } from "../ui-tailwind/toolbar/tw-toolbar";
@@ -103,6 +131,7 @@ type AccessibleRegionId = (typeof ACCESSIBLE_REGION_ORDER)[number];
103
131
 
104
132
  export function __createWordReviewEditorRefBridge(
105
133
  runtime: WordReviewEditorRuntime,
134
+ mountedSurface?: TwProseMirrorSurfaceRef | null,
106
135
  ): WordReviewEditorRef {
107
136
  return {
108
137
  focus: () => runtime.focus(),
@@ -125,6 +154,127 @@ export function __createWordReviewEditorRefBridge(
125
154
  getWarnings: () => runtime.getWarnings(),
126
155
  getComments: () => runtime.getRenderSnapshot().comments,
127
156
  getTrackedChanges: () => runtime.getRenderSnapshot().trackedChanges,
157
+ getFormattingState: () => getFormattingStateFromRenderSnapshot(runtime.getRenderSnapshot()),
158
+ toggleBold: () => {
159
+ applyRuntimeFormattingOperation(runtime, { type: "toggle", mark: "bold" });
160
+ },
161
+ toggleItalic: () => {
162
+ applyRuntimeFormattingOperation(runtime, { type: "toggle", mark: "italic" });
163
+ },
164
+ toggleUnderline: () => {
165
+ applyRuntimeFormattingOperation(runtime, { type: "toggle", mark: "underline" });
166
+ },
167
+ toggleStrikethrough: () => {
168
+ applyRuntimeFormattingOperation(runtime, {
169
+ type: "toggle",
170
+ mark: "strikethrough",
171
+ });
172
+ },
173
+ toggleSuperscript: () => {
174
+ applyRuntimeFormattingOperation(runtime, {
175
+ type: "toggle",
176
+ mark: "superscript",
177
+ });
178
+ },
179
+ toggleSubscript: () => {
180
+ applyRuntimeFormattingOperation(runtime, { type: "toggle", mark: "subscript" });
181
+ },
182
+ setFontFamily: (fontFamily) => {
183
+ applyRuntimeFormattingOperation(runtime, {
184
+ type: "set-font-family",
185
+ fontFamily,
186
+ });
187
+ },
188
+ setFontSize: (size) => {
189
+ applyRuntimeFormattingOperation(runtime, { type: "set-font-size", size });
190
+ },
191
+ setTextColor: (color) => {
192
+ applyRuntimeFormattingOperation(runtime, { type: "set-text-color", color });
193
+ },
194
+ setHighlightColor: (color) => {
195
+ applyRuntimeFormattingOperation(runtime, {
196
+ type: "set-highlight-color",
197
+ color,
198
+ });
199
+ },
200
+ setAlignment: (alignment) => {
201
+ applyRuntimeFormattingOperation(runtime, {
202
+ type: "set-alignment",
203
+ alignment,
204
+ });
205
+ },
206
+ indent: () => {
207
+ applyRuntimeFormattingOperation(runtime, { type: "indent" });
208
+ },
209
+ outdent: () => {
210
+ applyRuntimeFormattingOperation(runtime, { type: "outdent" });
211
+ },
212
+ insertPageBreak: () => {
213
+ applyRuntimeInsertPageBreak(runtime);
214
+ },
215
+ insertTable: (options) => {
216
+ applyRuntimeInsertTable(runtime, options);
217
+ },
218
+ insertImage: (options) => {
219
+ applyRuntimeInsertImage(runtime, options);
220
+ },
221
+ addRowBefore: () => {
222
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
223
+ type: "add-row-before",
224
+ });
225
+ },
226
+ addRowAfter: () => {
227
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
228
+ type: "add-row-after",
229
+ });
230
+ },
231
+ addColumnBefore: () => {
232
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
233
+ type: "add-column-before",
234
+ });
235
+ },
236
+ addColumnAfter: () => {
237
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
238
+ type: "add-column-after",
239
+ });
240
+ },
241
+ deleteRow: () => {
242
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
243
+ type: "delete-row",
244
+ });
245
+ },
246
+ deleteColumn: () => {
247
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
248
+ type: "delete-column",
249
+ });
250
+ },
251
+ deleteTable: () => {
252
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
253
+ type: "delete-table",
254
+ });
255
+ },
256
+ mergeCells: () => {
257
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
258
+ type: "merge-cells",
259
+ });
260
+ },
261
+ splitCell: () => {
262
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
263
+ type: "split-cell",
264
+ });
265
+ },
266
+ setCellBackground: (color) => {
267
+ applyRuntimeTableStructureOperation(runtime, mountedSurface ?? null, {
268
+ type: "set-cell-background",
269
+ color,
270
+ });
271
+ },
272
+ search: (query, options) =>
273
+ mountedSurface?.search(query, options) ??
274
+ searchSnapshotSurface(runtime.getRenderSnapshot(), query, options),
275
+ clearSearch: () => {
276
+ mountedSurface?.clearSearch();
277
+ },
128
278
  scrollToRevision: (revisionId: string) => {
129
279
  const revision = runtime.getRenderSnapshot().trackedChanges.revisions.find(
130
280
  (r) => r.revisionId === revisionId,
@@ -312,6 +462,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
312
462
  const [showTrackedChanges, setShowTrackedChanges] = useState(false);
313
463
  const [activeRevisionId, setActiveRevisionId] = useState<string | undefined>();
314
464
  const runtimeRef = useRef<WordReviewEditorRuntime | null>(null);
465
+ const surfaceRef = useRef<TwProseMirrorSurfaceRef | null>(null);
315
466
  const shellRef = useRef<HTMLDivElement | null>(null);
316
467
  const lastAnnouncedErrorIdRef = useRef<string | null>(null);
317
468
  const autosaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -547,6 +698,146 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
547
698
  getWarnings: () => activeRuntime.getWarnings(),
548
699
  getComments: () => activeRuntime.getRenderSnapshot().comments,
549
700
  getTrackedChanges: () => activeRuntime.getRenderSnapshot().trackedChanges,
701
+ getFormattingState: () =>
702
+ getFormattingStateFromRenderSnapshot(activeRuntime.getRenderSnapshot()),
703
+ toggleBold: () => {
704
+ applyRuntimeFormattingOperation(activeRuntime, {
705
+ type: "toggle",
706
+ mark: "bold",
707
+ });
708
+ },
709
+ toggleItalic: () => {
710
+ applyRuntimeFormattingOperation(activeRuntime, {
711
+ type: "toggle",
712
+ mark: "italic",
713
+ });
714
+ },
715
+ toggleUnderline: () => {
716
+ applyRuntimeFormattingOperation(activeRuntime, {
717
+ type: "toggle",
718
+ mark: "underline",
719
+ });
720
+ },
721
+ toggleStrikethrough: () => {
722
+ applyRuntimeFormattingOperation(activeRuntime, {
723
+ type: "toggle",
724
+ mark: "strikethrough",
725
+ });
726
+ },
727
+ toggleSuperscript: () => {
728
+ applyRuntimeFormattingOperation(activeRuntime, {
729
+ type: "toggle",
730
+ mark: "superscript",
731
+ });
732
+ },
733
+ toggleSubscript: () => {
734
+ applyRuntimeFormattingOperation(activeRuntime, {
735
+ type: "toggle",
736
+ mark: "subscript",
737
+ });
738
+ },
739
+ setFontFamily: (fontFamily) => {
740
+ applyRuntimeFormattingOperation(activeRuntime, {
741
+ type: "set-font-family",
742
+ fontFamily,
743
+ });
744
+ },
745
+ setFontSize: (size) => {
746
+ applyRuntimeFormattingOperation(activeRuntime, {
747
+ type: "set-font-size",
748
+ size,
749
+ });
750
+ },
751
+ setTextColor: (color) => {
752
+ applyRuntimeFormattingOperation(activeRuntime, {
753
+ type: "set-text-color",
754
+ color,
755
+ });
756
+ },
757
+ setHighlightColor: (color) => {
758
+ applyRuntimeFormattingOperation(activeRuntime, {
759
+ type: "set-highlight-color",
760
+ color,
761
+ });
762
+ },
763
+ setAlignment: (alignment) => {
764
+ applyRuntimeFormattingOperation(activeRuntime, {
765
+ type: "set-alignment",
766
+ alignment,
767
+ });
768
+ },
769
+ indent: () => {
770
+ applyRuntimeFormattingOperation(activeRuntime, { type: "indent" });
771
+ },
772
+ outdent: () => {
773
+ applyRuntimeFormattingOperation(activeRuntime, { type: "outdent" });
774
+ },
775
+ insertPageBreak: () => {
776
+ applyRuntimeInsertPageBreak(activeRuntime);
777
+ },
778
+ insertTable: (options) => {
779
+ applyRuntimeInsertTable(activeRuntime, options);
780
+ },
781
+ insertImage: (options) => {
782
+ applyRuntimeInsertImage(activeRuntime, options);
783
+ },
784
+ addRowBefore: () => {
785
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
786
+ type: "add-row-before",
787
+ });
788
+ },
789
+ addRowAfter: () => {
790
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
791
+ type: "add-row-after",
792
+ });
793
+ },
794
+ addColumnBefore: () => {
795
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
796
+ type: "add-column-before",
797
+ });
798
+ },
799
+ addColumnAfter: () => {
800
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
801
+ type: "add-column-after",
802
+ });
803
+ },
804
+ deleteRow: () => {
805
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
806
+ type: "delete-row",
807
+ });
808
+ },
809
+ deleteColumn: () => {
810
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
811
+ type: "delete-column",
812
+ });
813
+ },
814
+ deleteTable: () => {
815
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
816
+ type: "delete-table",
817
+ });
818
+ },
819
+ mergeCells: () => {
820
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
821
+ type: "merge-cells",
822
+ });
823
+ },
824
+ splitCell: () => {
825
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
826
+ type: "split-cell",
827
+ });
828
+ },
829
+ setCellBackground: (color) => {
830
+ applyRuntimeTableStructureOperation(activeRuntime, surfaceRef.current, {
831
+ type: "set-cell-background",
832
+ color,
833
+ });
834
+ },
835
+ search: (query, options) =>
836
+ surfaceRef.current?.search(query, options) ??
837
+ searchSnapshotSurface(activeRuntime.getRenderSnapshot(), query, options),
838
+ clearSearch: () => {
839
+ surfaceRef.current?.clearSearch();
840
+ },
550
841
  scrollToRevision: (revisionId: string) => {
551
842
  const revision = activeRuntime.getRenderSnapshot().trackedChanges.revisions.find(
552
843
  (r) => r.revisionId === revisionId,
@@ -862,6 +1153,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
862
1153
  {...reviewCallbacks}
863
1154
  document={
864
1155
  <TwProseMirrorSurface
1156
+ ref={surfaceRef}
865
1157
  currentUser={currentUser}
866
1158
  snapshot={snapshot}
867
1159
  reviewMode={reviewMode}
@@ -885,6 +1177,231 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
885
1177
  },
886
1178
  );
887
1179
 
1180
+ function applyRuntimeFormattingOperation(
1181
+ runtime: WordReviewEditorRuntime,
1182
+ operation:
1183
+ | { type: "toggle"; mark: "bold" | "italic" | "underline" | "strikethrough" | "superscript" | "subscript" }
1184
+ | { type: "set-font-family"; fontFamily: string | null }
1185
+ | { type: "set-font-size"; size: number | null }
1186
+ | { type: "set-text-color"; color: string | null }
1187
+ | { type: "set-highlight-color"; color: string | null }
1188
+ | { type: "set-alignment"; alignment: FormattingAlignment }
1189
+ | { type: "indent" }
1190
+ | { type: "outdent" },
1191
+ ): void {
1192
+ const snapshot = runtime.getRenderSnapshot();
1193
+ if (!snapshot.isReady || snapshot.readOnly || snapshot.fatalError) {
1194
+ return;
1195
+ }
1196
+
1197
+ const result = applyFormattingOperationToDocument(
1198
+ runtime.getPersistedSnapshot().canonicalDocument,
1199
+ snapshot,
1200
+ operation,
1201
+ );
1202
+ if (!result.changed) {
1203
+ return;
1204
+ }
1205
+
1206
+ runtime.dispatch({
1207
+ type: "document.replace",
1208
+ document: result.document,
1209
+ selection: toRuntimeSelectionSnapshot(result.selection),
1210
+ origin: {
1211
+ source: "api",
1212
+ timestamp: new Date().toISOString(),
1213
+ },
1214
+ });
1215
+ }
1216
+
1217
+ function applyRuntimeInsertPageBreak(runtime: WordReviewEditorRuntime): void {
1218
+ const snapshot = runtime.getRenderSnapshot();
1219
+ if (!canApplyRuntimeMutation(snapshot)) {
1220
+ return;
1221
+ }
1222
+
1223
+ const timestamp = new Date().toISOString();
1224
+ const result = insertPageBreakInDocument(
1225
+ runtime.getPersistedSnapshot().canonicalDocument,
1226
+ toRuntimeSelectionSnapshot(snapshot.selection),
1227
+ { timestamp },
1228
+ );
1229
+ dispatchRuntimeDocumentMutation(runtime, result, timestamp);
1230
+ }
1231
+
1232
+ function applyRuntimeInsertTable(
1233
+ runtime: WordReviewEditorRuntime,
1234
+ options: InsertTableOptions,
1235
+ ): void {
1236
+ const snapshot = runtime.getRenderSnapshot();
1237
+ if (!canApplyRuntimeMutation(snapshot)) {
1238
+ return;
1239
+ }
1240
+
1241
+ const timestamp = new Date().toISOString();
1242
+ const result = insertTableInDocument(
1243
+ runtime.getPersistedSnapshot().canonicalDocument,
1244
+ toRuntimeSelectionSnapshot(snapshot.selection),
1245
+ options,
1246
+ { timestamp },
1247
+ );
1248
+ dispatchRuntimeDocumentMutation(runtime, result, timestamp);
1249
+ }
1250
+
1251
+ function applyRuntimeInsertImage(
1252
+ runtime: WordReviewEditorRuntime,
1253
+ options: InsertImageOptions,
1254
+ ): void {
1255
+ const snapshot = runtime.getRenderSnapshot();
1256
+ if (!canApplyRuntimeMutation(snapshot)) {
1257
+ return;
1258
+ }
1259
+
1260
+ const timestamp = new Date().toISOString();
1261
+ try {
1262
+ const result = insertImageInDocument(
1263
+ runtime.getPersistedSnapshot().canonicalDocument,
1264
+ toRuntimeSelectionSnapshot(snapshot.selection),
1265
+ options.data,
1266
+ options.mimeType,
1267
+ options.width,
1268
+ options.height,
1269
+ {
1270
+ timestamp,
1271
+ altText: options.altText,
1272
+ },
1273
+ );
1274
+ dispatchRuntimeDocumentMutation(runtime, {
1275
+ changed: true,
1276
+ document: result.document,
1277
+ selection: result.selection,
1278
+ mapping: result.mapping,
1279
+ }, timestamp);
1280
+ } catch {
1281
+ return;
1282
+ }
1283
+ }
1284
+
1285
+ function applyRuntimeTableStructureOperation(
1286
+ runtime: WordReviewEditorRuntime,
1287
+ mountedSurface: TwProseMirrorSurfaceRef | null | undefined,
1288
+ operation:
1289
+ | { type: "add-row-before" }
1290
+ | { type: "add-row-after" }
1291
+ | { type: "add-column-before" }
1292
+ | { type: "add-column-after" }
1293
+ | { type: "delete-row" }
1294
+ | { type: "delete-column" }
1295
+ | { type: "delete-table" }
1296
+ | { type: "merge-cells" }
1297
+ | { type: "split-cell" }
1298
+ | { type: "set-cell-background"; color: string },
1299
+ ): void {
1300
+ const snapshot = runtime.getRenderSnapshot();
1301
+ if (!canApplyRuntimeMutation(snapshot)) {
1302
+ return;
1303
+ }
1304
+
1305
+ const timestamp = new Date().toISOString();
1306
+ const result = applyTableStructureOperation(
1307
+ runtime.getPersistedSnapshot().canonicalDocument,
1308
+ snapshot,
1309
+ mountedSurface?.getTableSelection() ?? null,
1310
+ operation,
1311
+ );
1312
+ dispatchRuntimeDocumentMutation(runtime, result, timestamp);
1313
+ }
1314
+
1315
+ function canApplyRuntimeMutation(snapshot: RuntimeRenderSnapshot): boolean {
1316
+ return snapshot.isReady && !snapshot.readOnly && !snapshot.fatalError;
1317
+ }
1318
+
1319
+ function dispatchRuntimeDocumentMutation(
1320
+ runtime: WordReviewEditorRuntime,
1321
+ result: {
1322
+ changed: boolean;
1323
+ document: PersistedEditorSnapshot["canonicalDocument"];
1324
+ selection: InternalSelectionSnapshot;
1325
+ mapping?: TransactionMapping;
1326
+ },
1327
+ timestamp: string,
1328
+ ): void {
1329
+ if (!result.changed) {
1330
+ return;
1331
+ }
1332
+
1333
+ runtime.dispatch({
1334
+ type: "document.replace",
1335
+ document: {
1336
+ ...result.document,
1337
+ updatedAt: timestamp,
1338
+ },
1339
+ mapping: result.mapping,
1340
+ selection: result.selection,
1341
+ origin: {
1342
+ source: "api",
1343
+ timestamp,
1344
+ },
1345
+ });
1346
+ }
1347
+
1348
+ function searchSnapshotSurface(
1349
+ snapshot: RuntimeRenderSnapshot,
1350
+ query: string,
1351
+ options: SearchOptions = {},
1352
+ ): SearchResultSnapshot[] {
1353
+ const normalizedQuery = query.trim();
1354
+ if (!normalizedQuery || !snapshot.surface) {
1355
+ return [];
1356
+ }
1357
+
1358
+ const rawResults = findSearchMatches(
1359
+ snapshot.surface.plainText,
1360
+ normalizedQuery,
1361
+ options,
1362
+ ).slice(0, options.limit ?? Number.POSITIVE_INFINITY);
1363
+ const activeResultIndex = getActiveSearchResultIndex(rawResults, snapshot.selection);
1364
+
1365
+ return rawResults.map((result, index) => ({
1366
+ resultId: `search-result-${index}`,
1367
+ anchor: {
1368
+ kind: "range",
1369
+ from: result.from,
1370
+ to: result.to,
1371
+ assoc: {
1372
+ start: -1,
1373
+ end: 1,
1374
+ },
1375
+ },
1376
+ excerpt: createSearchExcerpt(
1377
+ snapshot.surface?.plainText ?? "",
1378
+ result.from,
1379
+ result.to,
1380
+ ),
1381
+ isActive: index === activeResultIndex,
1382
+ }));
1383
+ }
1384
+
1385
+ function getActiveSearchResultIndex(
1386
+ results: Array<{ from: number; to: number }>,
1387
+ selection: PublicSelectionSnapshot,
1388
+ ): number {
1389
+ if (results.length === 0) {
1390
+ return -1;
1391
+ }
1392
+
1393
+ const selectionFrom = Math.min(selection.anchor, selection.head);
1394
+ const selectionTo = Math.max(selection.anchor, selection.head);
1395
+ const activeIndex = results.findIndex((result) => {
1396
+ if (selectionFrom === selectionTo) {
1397
+ return selectionFrom >= result.from && selectionFrom <= result.to;
1398
+ }
1399
+ return selectionFrom < result.to && selectionTo > result.from;
1400
+ });
1401
+
1402
+ return activeIndex >= 0 ? activeIndex : 0;
1403
+ }
1404
+
888
1405
  function applyRegionAttributes(shell: HTMLElement): void {
889
1406
  const toolbar = shell.querySelector<HTMLElement>("header");
890
1407
  if (toolbar) {
@@ -1,5 +1,5 @@
1
1
  import { Fragment, type Node as PMNode } from "prosemirror-model";
2
- import { EditorState, type Plugin, TextSelection } from "prosemirror-state";
2
+ import { EditorState, type Plugin, Selection, TextSelection } from "prosemirror-state";
3
3
 
4
4
  import type {
5
5
  EditorSurfaceSnapshot,
@@ -43,12 +43,13 @@ export function createPMStateFromSnapshot(
43
43
  positionMap.pmDocSize - 1,
44
44
  );
45
45
 
46
- let pmSelection: TextSelection;
46
+ let pmSelection: Selection;
47
47
  try {
48
- pmSelection = TextSelection.create(doc, pmAnchor, pmHead);
48
+ pmSelection = TextSelection.between(doc.resolve(pmAnchor), doc.resolve(pmHead));
49
49
  } catch {
50
- // If the position is invalid (e.g., inside an atom), fall back to start
51
- pmSelection = TextSelection.create(doc, 1);
50
+ // If the mapped runtime selection is invalid or lands in a non-text block,
51
+ // let ProseMirror choose the nearest valid starting selection.
52
+ pmSelection = Selection.atStart(doc);
52
53
  }
53
54
 
54
55
  const state = EditorState.create({
@@ -258,6 +259,7 @@ function buildTable(
258
259
  rowspan: cell.rowspan,
259
260
  gridSpan: cell.gridSpan,
260
261
  verticalMerge: cell.verticalMerge,
262
+ backgroundColor: cell.backgroundColor ?? null,
261
263
  },
262
264
  Fragment.from(cellContent),
263
265
  ),