@apollohg/react-native-prose-editor 0.5.16 → 0.5.17

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.
@@ -3,6 +3,7 @@ import { type SchemaDefinition } from './schemas';
3
3
  export interface NativeEditorModule {
4
4
  editorCreate(configJson: string): number;
5
5
  editorDestroy(editorId: number): void;
6
+ editorPrepareForCommand?(editorId: number): string;
6
7
  collaborationSessionCreate(configJson: string): number;
7
8
  collaborationSessionDestroy(sessionId: number): void;
8
9
  collaborationSessionGetDocumentJson(sessionId: number): string;
@@ -123,6 +124,10 @@ export interface EditorUpdate {
123
124
  historyState: HistoryState;
124
125
  documentVersion?: number;
125
126
  }
127
+ export interface ParseUpdateOptions {
128
+ rejectSameDocumentVersion?: boolean;
129
+ rejectVersionlessAfterDocumentVersion?: boolean;
130
+ }
126
131
  export interface ContentSnapshot {
127
132
  html: string;
128
133
  json: DocumentJSON;
@@ -142,6 +147,11 @@ export interface CollaborationResult {
142
147
  peersChanged: boolean;
143
148
  peers?: CollaborationPeer[];
144
149
  }
150
+ export type CommandBlockedReason = 'composition' | 'detached' | 'pendingUpdate' | 'destroyed' | 'unknown';
151
+ export interface CommandBlockedInfo {
152
+ blocked: boolean;
153
+ reason: CommandBlockedReason | null;
154
+ }
145
155
  export type EncodedCollaborationStateInput = Uint8Array | readonly number[] | string;
146
156
  export declare function normalizeActiveState(raw: unknown): ActiveState;
147
157
  export declare function parseEditorUpdateJson(json: string, previousRenderBlocks?: RenderElement[][]): EditorUpdate | null;
@@ -159,6 +169,11 @@ export declare class NativeEditorBridge {
159
169
  private _cachedHtml;
160
170
  private _cachedJsonString;
161
171
  private _renderBlocksCache;
172
+ private _lastCommandBlocked;
173
+ private _lastCommandBlockedReason;
174
+ private _lastCommandPreflightUpdate;
175
+ private _lastAcceptedUpdateJson;
176
+ private _hasSeenDocumentVersion;
162
177
  private constructor();
163
178
  /** Create a new editor instance backed by the Rust engine. */
164
179
  static create(config?: {
@@ -170,18 +185,29 @@ export declare class NativeEditorBridge {
170
185
  get editorId(): number;
171
186
  /** Whether this bridge has been destroyed. */
172
187
  get isDestroyed(): boolean;
188
+ consumeLastCommandBlocked(): boolean;
189
+ consumeLastCommandBlockedReason(): CommandBlockedReason | null;
190
+ consumeLastCommandBlockedInfo(): CommandBlockedInfo;
191
+ consumeLastCommandPreflightUpdate(): EditorUpdate | null;
192
+ prepareForNativeCommand(): boolean;
173
193
  /** Destroy the editor instance and free native resources. */
174
194
  destroy(): void;
175
195
  /** Set content from HTML. Returns render elements for display. */
176
196
  setHtml(html: string): RenderElement[];
177
197
  /** Get content as HTML. */
178
198
  getHtml(): string;
199
+ /** Get cached HTML without making a native roundtrip. */
200
+ getCachedHtml(): string | null;
179
201
  /** Set content from ProseMirror JSON. Returns render elements. */
180
202
  setJson(doc: DocumentJSON): RenderElement[];
181
203
  /** Set content from a serialized ProseMirror JSON string. Returns render elements. */
182
204
  setJsonString(jsonString: string): RenderElement[];
183
205
  /** Get content as raw ProseMirror JSON string. */
184
206
  getJsonString(): string;
207
+ /** Get cached raw ProseMirror JSON without making a native roundtrip. */
208
+ getCachedJsonString(): string | null;
209
+ /** Get cached ProseMirror JSON without making a native roundtrip. */
210
+ getCachedJson(): DocumentJSON | null;
185
211
  /** Get content as ProseMirror JSON. */
186
212
  getJson(): DocumentJSON;
187
213
  /** Get both HTML and JSON content in one native roundtrip. */
@@ -200,6 +226,8 @@ export declare class NativeEditorBridge {
200
226
  setMarkAtSelectionScalar(scalarAnchor: number, scalarHead: number, markType: string, attrs: Record<string, unknown>): EditorUpdate | null;
201
227
  /** Remove a mark from the current selection. */
202
228
  unsetMark(markType: string): EditorUpdate | null;
229
+ /** Remove a mark at an explicit scalar selection. */
230
+ unsetMarkAtSelectionScalar(scalarAnchor: number, scalarHead: number, markType: string): EditorUpdate | null;
203
231
  /** Toggle blockquote wrapping for the current block selection. */
204
232
  toggleBlockquote(): EditorUpdate | null;
205
233
  /** Toggle a heading level on the current block selection. */
@@ -228,6 +256,8 @@ export declare class NativeEditorBridge {
228
256
  insertContentJson(doc: DocumentJSON): EditorUpdate | null;
229
257
  /** Insert JSON content at an explicit scalar selection. */
230
258
  insertContentJsonAtSelectionScalar(scalarAnchor: number, scalarHead: number, doc: DocumentJSON): EditorUpdate | null;
259
+ /** Insert lazily-built JSON content at an explicit scalar selection. */
260
+ insertContentJsonAtSelectionScalarLazy(scalarAnchor: number, scalarHead: number, resolveDoc: () => DocumentJSON): EditorUpdate | null;
231
261
  /** Replace entire document with HTML via transaction (preserves undo history). */
232
262
  replaceHtml(html: string): EditorUpdate | null;
233
263
  /** Replace entire document with JSON via transaction (preserves undo history). */
@@ -252,9 +282,12 @@ export declare class NativeEditorBridge {
252
282
  outdentListItem(): EditorUpdate | null;
253
283
  /** Insert a void node (e.g. 'horizontalRule') at the current selection. */
254
284
  insertNode(nodeType: string): EditorUpdate | null;
255
- parseUpdateJson(json: string): EditorUpdate | null;
285
+ parseUpdateJson(json: string, options?: ParseUpdateOptions): EditorUpdate | null;
256
286
  private noteUpdate;
287
+ private shouldRejectUpdate;
257
288
  private parseAndNoteUpdate;
289
+ private prepareForCommand;
290
+ private runPreparedCommand;
258
291
  private invalidateContentCaches;
259
292
  private assertNotDestroyed;
260
293
  private currentScalarSelection;
@@ -8,6 +8,7 @@ exports.decodeCollaborationStateBase64 = decodeCollaborationStateBase64;
8
8
  exports.parseCollaborationResultJson = parseCollaborationResultJson;
9
9
  exports._resetNativeModuleCache = _resetNativeModuleCache;
10
10
  const expo_modules_core_1 = require("expo-modules-core");
11
+ const react_native_1 = require("react-native");
11
12
  const schemas_1 = require("./schemas");
12
13
  const ERR_DESTROYED = 'NativeEditorBridge: editor has been destroyed';
13
14
  const ERR_NATIVE_RESPONSE = 'NativeEditorBridge: invalid JSON response from native module';
@@ -244,6 +245,34 @@ function normalizeDocumentJsonString(jsonString, schema) {
244
245
  return jsonString;
245
246
  }
246
247
  }
248
+ function parseCommandPreparationJson(json) {
249
+ if (!json)
250
+ return { ready: true };
251
+ let parsed;
252
+ try {
253
+ parsed = JSON.parse(json);
254
+ }
255
+ catch {
256
+ throw new Error(ERR_NATIVE_RESPONSE);
257
+ }
258
+ if (typeof parsed.error === 'string') {
259
+ throw new Error(`NativeEditorBridge: ${parsed.error}`);
260
+ }
261
+ const rawBlockedReason = parsed.blockedReason;
262
+ const blockedReason = rawBlockedReason === 'composition' ||
263
+ rawBlockedReason === 'detached' ||
264
+ rawBlockedReason === 'pendingUpdate' ||
265
+ rawBlockedReason === 'destroyed'
266
+ ? rawBlockedReason
267
+ : rawBlockedReason === 'unknown'
268
+ ? 'unknown'
269
+ : undefined;
270
+ return {
271
+ ready: parsed.ready !== false,
272
+ updateJSON: typeof parsed.updateJSON === 'string' ? parsed.updateJSON : undefined,
273
+ blockedReason,
274
+ };
275
+ }
247
276
  function parseCollaborationResultJson(json) {
248
277
  if (!json || json === '') {
249
278
  return {
@@ -293,6 +322,11 @@ class NativeEditorBridge {
293
322
  this._cachedHtml = null;
294
323
  this._cachedJsonString = null;
295
324
  this._renderBlocksCache = null;
325
+ this._lastCommandBlocked = false;
326
+ this._lastCommandBlockedReason = null;
327
+ this._lastCommandPreflightUpdate = null;
328
+ this._lastAcceptedUpdateJson = null;
329
+ this._hasSeenDocumentVersion = false;
296
330
  this._editorId = editorId;
297
331
  this._schema = schema;
298
332
  }
@@ -325,12 +359,45 @@ class NativeEditorBridge {
325
359
  get isDestroyed() {
326
360
  return this._destroyed;
327
361
  }
362
+ consumeLastCommandBlocked() {
363
+ const blocked = this._lastCommandBlocked;
364
+ this._lastCommandBlocked = false;
365
+ this._lastCommandBlockedReason = null;
366
+ return blocked;
367
+ }
368
+ consumeLastCommandBlockedReason() {
369
+ const reason = this._lastCommandBlockedReason;
370
+ this._lastCommandBlocked = false;
371
+ this._lastCommandBlockedReason = null;
372
+ return reason;
373
+ }
374
+ consumeLastCommandBlockedInfo() {
375
+ const info = {
376
+ blocked: this._lastCommandBlocked,
377
+ reason: this._lastCommandBlockedReason,
378
+ };
379
+ this._lastCommandBlocked = false;
380
+ this._lastCommandBlockedReason = null;
381
+ return info;
382
+ }
383
+ consumeLastCommandPreflightUpdate() {
384
+ const update = this._lastCommandPreflightUpdate;
385
+ this._lastCommandPreflightUpdate = null;
386
+ return update;
387
+ }
388
+ prepareForNativeCommand() {
389
+ this.assertNotDestroyed();
390
+ return this.prepareForCommand();
391
+ }
328
392
  /** Destroy the editor instance and free native resources. */
329
393
  destroy() {
330
394
  if (this._destroyed)
331
395
  return;
332
396
  this._destroyed = true;
333
397
  this._renderBlocksCache = null;
398
+ this._lastCommandBlocked = false;
399
+ this._lastCommandBlockedReason = null;
400
+ this._lastCommandPreflightUpdate = null;
334
401
  getNativeModule().editorDestroy(this._editorId);
335
402
  }
336
403
  /** Set content from HTML. Returns render elements for display. */
@@ -351,6 +418,11 @@ class NativeEditorBridge {
351
418
  this._cachedHtml = { version: this._documentVersion, value: html };
352
419
  return html;
353
420
  }
421
+ /** Get cached HTML without making a native roundtrip. */
422
+ getCachedHtml() {
423
+ this.assertNotDestroyed();
424
+ return this._cachedHtml?.value ?? null;
425
+ }
354
426
  /** Set content from ProseMirror JSON. Returns render elements. */
355
427
  setJson(doc) {
356
428
  return this.setJsonString(JSON.stringify((0, schemas_1.normalizeDocumentJson)(doc, this._schema)));
@@ -374,6 +446,16 @@ class NativeEditorBridge {
374
446
  this._cachedJsonString = { version: this._documentVersion, value: json };
375
447
  return json;
376
448
  }
449
+ /** Get cached raw ProseMirror JSON without making a native roundtrip. */
450
+ getCachedJsonString() {
451
+ this.assertNotDestroyed();
452
+ return this._cachedJsonString?.value ?? null;
453
+ }
454
+ /** Get cached ProseMirror JSON without making a native roundtrip. */
455
+ getCachedJson() {
456
+ const json = this.getCachedJsonString();
457
+ return json == null ? null : parseDocumentJSON(json);
458
+ }
377
459
  /** Get content as ProseMirror JSON. */
378
460
  getJson() {
379
461
  return parseDocumentJSON(this.getJsonString());
@@ -399,63 +481,68 @@ class NativeEditorBridge {
399
481
  /** Insert text at a document position. Returns the full update. */
400
482
  insertText(pos, text) {
401
483
  this.assertNotDestroyed();
402
- const json = getNativeModule().editorInsertText(this._editorId, pos, text);
403
- return this.parseAndNoteUpdate(json);
484
+ return this.runPreparedCommand(() => getNativeModule().editorInsertText(this._editorId, pos, text));
404
485
  }
405
486
  /** Delete a range [from, to). Returns the full update. */
406
487
  deleteRange(from, to) {
407
488
  this.assertNotDestroyed();
408
- const json = getNativeModule().editorDeleteRange(this._editorId, from, to);
409
- return this.parseAndNoteUpdate(json);
489
+ return this.runPreparedCommand(() => getNativeModule().editorDeleteRange(this._editorId, from, to));
410
490
  }
411
491
  /** Replace the current selection with text atomically. */
412
492
  replaceSelectionText(text) {
413
493
  this.assertNotDestroyed();
414
- const json = getNativeModule().editorReplaceSelectionText(this._editorId, text);
415
- return this.parseAndNoteUpdate(json);
494
+ return this.runPreparedCommand(() => getNativeModule().editorReplaceSelectionText(this._editorId, text));
416
495
  }
417
496
  /** Toggle a mark (bold, italic, etc.) on the current selection. */
418
497
  toggleMark(markType) {
419
498
  this.assertNotDestroyed();
420
- const scalarSelection = this.currentScalarSelection();
421
- const json = scalarSelection
422
- ? getNativeModule().editorToggleMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
423
- : getNativeModule().editorToggleMark(this._editorId, markType);
424
- return this.parseAndNoteUpdate(json);
499
+ return this.runPreparedCommand(() => {
500
+ const scalarSelection = this.currentScalarSelection();
501
+ return scalarSelection
502
+ ? getNativeModule().editorToggleMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
503
+ : getNativeModule().editorToggleMark(this._editorId, markType);
504
+ }, { refreshSelectionAfterPreflight: true });
425
505
  }
426
506
  /** Set a mark with attrs on the current selection. */
427
507
  setMark(markType, attrs) {
428
508
  this.assertNotDestroyed();
429
509
  const attrsJson = JSON.stringify(attrs);
430
- const scalarSelection = this.currentScalarSelection();
431
- const json = scalarSelection
432
- ? getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType, attrsJson)
433
- : getNativeModule().editorSetMark(this._editorId, markType, attrsJson);
434
- return this.parseAndNoteUpdate(json);
510
+ return this.runPreparedCommand(() => {
511
+ const scalarSelection = this.currentScalarSelection();
512
+ return scalarSelection
513
+ ? getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType, attrsJson)
514
+ : getNativeModule().editorSetMark(this._editorId, markType, attrsJson);
515
+ }, { refreshSelectionAfterPreflight: true });
435
516
  }
436
517
  /** Set a mark with attrs at an explicit scalar selection. */
437
518
  setMarkAtSelectionScalar(scalarAnchor, scalarHead, markType, attrs) {
438
519
  this.assertNotDestroyed();
439
- const json = getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, markType, JSON.stringify(attrs));
440
- return this.parseAndNoteUpdate(json);
520
+ return this.runPreparedCommand(() => getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, markType, JSON.stringify(attrs)), { cancelIfPreflightUpdated: true });
441
521
  }
442
522
  /** Remove a mark from the current selection. */
443
523
  unsetMark(markType) {
444
524
  this.assertNotDestroyed();
445
- const scalarSelection = this.currentScalarSelection();
446
- const json = scalarSelection
447
- ? getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
448
- : getNativeModule().editorUnsetMark(this._editorId, markType);
449
- return this.parseAndNoteUpdate(json);
525
+ return this.runPreparedCommand(() => {
526
+ const scalarSelection = this.currentScalarSelection();
527
+ return scalarSelection
528
+ ? getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
529
+ : getNativeModule().editorUnsetMark(this._editorId, markType);
530
+ }, { refreshSelectionAfterPreflight: true });
531
+ }
532
+ /** Remove a mark at an explicit scalar selection. */
533
+ unsetMarkAtSelectionScalar(scalarAnchor, scalarHead, markType) {
534
+ this.assertNotDestroyed();
535
+ return this.runPreparedCommand(() => getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, markType), { cancelIfPreflightUpdated: true });
450
536
  }
451
537
  /** Toggle blockquote wrapping for the current block selection. */
452
538
  toggleBlockquote() {
453
539
  this.assertNotDestroyed();
454
- const scalarSelection = this.currentScalarSelection();
455
- const json = scalarSelection
456
- ? getNativeModule().editorToggleBlockquoteAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
457
- : getNativeModule().editorToggleBlockquote(this._editorId);
458
- return this.parseAndNoteUpdate(json);
540
+ return this.runPreparedCommand(() => {
541
+ const scalarSelection = this.currentScalarSelection();
542
+ return scalarSelection
543
+ ? getNativeModule().editorToggleBlockquoteAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
544
+ : getNativeModule().editorToggleBlockquote(this._editorId);
545
+ }, { refreshSelectionAfterPreflight: true });
459
546
  }
460
547
  /** Toggle a heading level on the current block selection. */
461
548
  toggleHeading(level) {
@@ -463,11 +550,12 @@ class NativeEditorBridge {
463
550
  if (!Number.isInteger(level) || level < 1 || level > 6) {
464
551
  throw new Error('NativeEditorBridge: invalid heading level');
465
552
  }
466
- const scalarSelection = this.currentScalarSelection();
467
- const json = scalarSelection
468
- ? getNativeModule().editorToggleHeadingAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, level)
469
- : getNativeModule().editorToggleHeading(this._editorId, level);
470
- return this.parseAndNoteUpdate(json);
553
+ return this.runPreparedCommand(() => {
554
+ const scalarSelection = this.currentScalarSelection();
555
+ return scalarSelection
556
+ ? getNativeModule().editorToggleHeadingAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, level)
557
+ : getNativeModule().editorToggleHeading(this._editorId, level);
558
+ }, { refreshSelectionAfterPreflight: true });
471
559
  }
472
560
  /** Set the document selection by anchor and head positions. */
473
561
  setSelection(anchor, head) {
@@ -522,32 +610,31 @@ class NativeEditorBridge {
522
610
  /** Split the block at a position (Enter key). */
523
611
  splitBlock(pos) {
524
612
  this.assertNotDestroyed();
525
- const json = getNativeModule().editorSplitBlock(this._editorId, pos);
526
- return this.parseAndNoteUpdate(json);
613
+ return this.runPreparedCommand(() => getNativeModule().editorSplitBlock(this._editorId, pos));
527
614
  }
528
615
  /** Insert HTML content at the current selection. */
529
616
  insertContentHtml(html) {
530
617
  this.assertNotDestroyed();
531
- const json = getNativeModule().editorInsertContentHtml(this._editorId, html);
532
- return this.parseAndNoteUpdate(json);
618
+ return this.runPreparedCommand(() => getNativeModule().editorInsertContentHtml(this._editorId, html));
533
619
  }
534
620
  /** Insert JSON content at the current selection. */
535
621
  insertContentJson(doc) {
536
622
  this.assertNotDestroyed();
537
- const json = getNativeModule().editorInsertContentJson(this._editorId, JSON.stringify(doc));
538
- return this.parseAndNoteUpdate(json);
623
+ return this.runPreparedCommand(() => getNativeModule().editorInsertContentJson(this._editorId, JSON.stringify(doc)));
539
624
  }
540
625
  /** Insert JSON content at an explicit scalar selection. */
541
626
  insertContentJsonAtSelectionScalar(scalarAnchor, scalarHead, doc) {
627
+ return this.insertContentJsonAtSelectionScalarLazy(scalarAnchor, scalarHead, () => doc);
628
+ }
629
+ /** Insert lazily-built JSON content at an explicit scalar selection. */
630
+ insertContentJsonAtSelectionScalarLazy(scalarAnchor, scalarHead, resolveDoc) {
542
631
  this.assertNotDestroyed();
543
- const json = getNativeModule().editorInsertContentJsonAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, JSON.stringify(doc));
544
- return this.parseAndNoteUpdate(json);
632
+ return this.runPreparedCommand(() => getNativeModule().editorInsertContentJsonAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, JSON.stringify(resolveDoc())), { cancelIfPreflightUpdated: true });
545
633
  }
546
634
  /** Replace entire document with HTML via transaction (preserves undo history). */
547
635
  replaceHtml(html) {
548
636
  this.assertNotDestroyed();
549
- const json = getNativeModule().editorReplaceHtml(this._editorId, html);
550
- return this.parseAndNoteUpdate(json);
637
+ return this.runPreparedCommand(() => getNativeModule().editorReplaceHtml(this._editorId, html));
551
638
  }
552
639
  /** Replace entire document with JSON via transaction (preserves undo history). */
553
640
  replaceJson(doc) {
@@ -557,20 +644,17 @@ class NativeEditorBridge {
557
644
  replaceJsonString(jsonString) {
558
645
  this.assertNotDestroyed();
559
646
  const normalizedJsonString = normalizeDocumentJsonString(jsonString, this._schema);
560
- const json = getNativeModule().editorReplaceJson(this._editorId, normalizedJsonString);
561
- return this.parseAndNoteUpdate(json);
647
+ return this.runPreparedCommand(() => getNativeModule().editorReplaceJson(this._editorId, normalizedJsonString));
562
648
  }
563
649
  /** Undo the last operation. Returns update or null if nothing to undo. */
564
650
  undo() {
565
651
  this.assertNotDestroyed();
566
- const json = getNativeModule().editorUndo(this._editorId);
567
- return this.parseAndNoteUpdate(json);
652
+ return this.runPreparedCommand(() => getNativeModule().editorUndo(this._editorId));
568
653
  }
569
654
  /** Redo the last undone operation. Returns update or null if nothing to redo. */
570
655
  redo() {
571
656
  this.assertNotDestroyed();
572
- const json = getNativeModule().editorRedo(this._editorId);
573
- return this.parseAndNoteUpdate(json);
657
+ return this.runPreparedCommand(() => getNativeModule().editorRedo(this._editorId));
574
658
  }
575
659
  /** Check if undo is available. */
576
660
  canUndo() {
@@ -585,56 +669,61 @@ class NativeEditorBridge {
585
669
  /** Toggle a list type on the current selection. Wraps if not in list, unwraps if already in that list type. */
586
670
  toggleList(listType) {
587
671
  this.assertNotDestroyed();
588
- const isActive = this.getCurrentState()?.activeState?.nodes?.[listType] === true;
589
- const scalarSelection = this.currentScalarSelection();
590
- const json = isActive
591
- ? scalarSelection
592
- ? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
593
- : getNativeModule().editorUnwrapFromList(this._editorId)
594
- : scalarSelection
595
- ? getNativeModule().editorWrapInListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, listType)
596
- : getNativeModule().editorWrapInList(this._editorId, listType);
597
- return this.parseAndNoteUpdate(json);
672
+ return this.runPreparedCommand(() => {
673
+ const isActive = this.getCurrentState()?.activeState?.nodes?.[listType] === true;
674
+ const scalarSelection = this.currentScalarSelection();
675
+ return isActive
676
+ ? scalarSelection
677
+ ? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
678
+ : getNativeModule().editorUnwrapFromList(this._editorId)
679
+ : scalarSelection
680
+ ? getNativeModule().editorWrapInListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, listType)
681
+ : getNativeModule().editorWrapInList(this._editorId, listType);
682
+ }, { refreshSelectionAfterPreflight: true });
598
683
  }
599
684
  /** Unwrap the current list item back to a paragraph. */
600
685
  unwrapFromList() {
601
686
  this.assertNotDestroyed();
602
- const scalarSelection = this.currentScalarSelection();
603
- const json = scalarSelection
604
- ? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
605
- : getNativeModule().editorUnwrapFromList(this._editorId);
606
- return this.parseAndNoteUpdate(json);
687
+ return this.runPreparedCommand(() => {
688
+ const scalarSelection = this.currentScalarSelection();
689
+ return scalarSelection
690
+ ? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
691
+ : getNativeModule().editorUnwrapFromList(this._editorId);
692
+ }, { refreshSelectionAfterPreflight: true });
607
693
  }
608
694
  /** Indent the current list item into a nested list. */
609
695
  indentListItem() {
610
696
  this.assertNotDestroyed();
611
- const scalarSelection = this.currentScalarSelection();
612
- const json = scalarSelection
613
- ? getNativeModule().editorIndentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
614
- : getNativeModule().editorIndentListItem(this._editorId);
615
- return this.parseAndNoteUpdate(json);
697
+ return this.runPreparedCommand(() => {
698
+ const scalarSelection = this.currentScalarSelection();
699
+ return scalarSelection
700
+ ? getNativeModule().editorIndentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
701
+ : getNativeModule().editorIndentListItem(this._editorId);
702
+ }, { refreshSelectionAfterPreflight: true });
616
703
  }
617
704
  /** Outdent the current list item to the parent list level. */
618
705
  outdentListItem() {
619
706
  this.assertNotDestroyed();
620
- const scalarSelection = this.currentScalarSelection();
621
- const json = scalarSelection
622
- ? getNativeModule().editorOutdentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
623
- : getNativeModule().editorOutdentListItem(this._editorId);
624
- return this.parseAndNoteUpdate(json);
707
+ return this.runPreparedCommand(() => {
708
+ const scalarSelection = this.currentScalarSelection();
709
+ return scalarSelection
710
+ ? getNativeModule().editorOutdentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
711
+ : getNativeModule().editorOutdentListItem(this._editorId);
712
+ }, { refreshSelectionAfterPreflight: true });
625
713
  }
626
714
  /** Insert a void node (e.g. 'horizontalRule') at the current selection. */
627
715
  insertNode(nodeType) {
628
716
  this.assertNotDestroyed();
629
- const scalarSelection = this.currentScalarSelection();
630
- const json = scalarSelection
631
- ? getNativeModule().editorInsertNodeAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, nodeType)
632
- : getNativeModule().editorInsertNode(this._editorId, nodeType);
633
- return this.parseAndNoteUpdate(json);
717
+ return this.runPreparedCommand(() => {
718
+ const scalarSelection = this.currentScalarSelection();
719
+ return scalarSelection
720
+ ? getNativeModule().editorInsertNodeAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, nodeType)
721
+ : getNativeModule().editorInsertNode(this._editorId, nodeType);
722
+ }, { refreshSelectionAfterPreflight: true });
634
723
  }
635
- parseUpdateJson(json) {
724
+ parseUpdateJson(json, options) {
636
725
  this.assertNotDestroyed();
637
- return this.parseAndNoteUpdate(json);
726
+ return this.parseAndNoteUpdate(json, options);
638
727
  }
639
728
  noteUpdate(update) {
640
729
  if (!update) {
@@ -648,17 +737,88 @@ class NativeEditorBridge {
648
737
  this.invalidateContentCaches();
649
738
  return;
650
739
  }
740
+ this._hasSeenDocumentVersion = true;
651
741
  if (update.documentVersion !== this._documentVersion) {
652
742
  this._documentVersion = update.documentVersion;
653
743
  this.invalidateContentCaches();
654
744
  }
655
745
  }
656
- parseAndNoteUpdate(json) {
746
+ shouldRejectUpdate(update, json, options) {
747
+ if (!update) {
748
+ return false;
749
+ }
750
+ if (typeof update.documentVersion !== 'number') {
751
+ return (options?.rejectVersionlessAfterDocumentVersion === true &&
752
+ react_native_1.Platform.OS === 'android' &&
753
+ this._hasSeenDocumentVersion);
754
+ }
755
+ if (react_native_1.Platform.OS === 'android') {
756
+ this._hasSeenDocumentVersion = true;
757
+ }
758
+ if (update.documentVersion < this._documentVersion) {
759
+ return true;
760
+ }
761
+ if (options?.rejectSameDocumentVersion === true) {
762
+ return (update.documentVersion === this._documentVersion &&
763
+ json === this._lastAcceptedUpdateJson);
764
+ }
765
+ return false;
766
+ }
767
+ parseAndNoteUpdate(json, options) {
657
768
  let update = parseEditorUpdateJson(json, this._renderBlocksCache ?? undefined);
769
+ if (this.shouldRejectUpdate(update, json, options)) {
770
+ return null;
771
+ }
658
772
  if (update?.renderPatch && !update.renderBlocks) {
659
- update = parseEditorUpdateJson(getNativeModule().editorGetCurrentState(this._editorId), this._renderBlocksCache ?? undefined);
773
+ json = getNativeModule().editorGetCurrentState(this._editorId);
774
+ update = parseEditorUpdateJson(json, this._renderBlocksCache ?? undefined);
775
+ if (this.shouldRejectUpdate(update, json, options)) {
776
+ return null;
777
+ }
660
778
  }
661
779
  this.noteUpdate(update);
780
+ if (update) {
781
+ this._lastAcceptedUpdateJson = json;
782
+ }
783
+ return update;
784
+ }
785
+ prepareForCommand() {
786
+ this._lastCommandBlocked = false;
787
+ this._lastCommandBlockedReason = null;
788
+ this._lastCommandPreflightUpdate = null;
789
+ const nativeModule = getNativeModule();
790
+ const prepareForCommand = nativeModule.editorPrepareForCommand;
791
+ if (typeof prepareForCommand !== 'function') {
792
+ return true;
793
+ }
794
+ const preparation = parseCommandPreparationJson(prepareForCommand(this._editorId));
795
+ if (preparation.updateJSON) {
796
+ this._lastCommandPreflightUpdate = this.parseAndNoteUpdate(preparation.updateJSON, {
797
+ rejectVersionlessAfterDocumentVersion: true,
798
+ });
799
+ }
800
+ if (!preparation.ready) {
801
+ this._lastCommandBlocked = true;
802
+ this._lastCommandBlockedReason = preparation.blockedReason ?? 'unknown';
803
+ return false;
804
+ }
805
+ return true;
806
+ }
807
+ runPreparedCommand(mutate, options) {
808
+ if (!this.prepareForCommand()) {
809
+ return null;
810
+ }
811
+ if (options?.cancelIfPreflightUpdated === true &&
812
+ this._lastCommandPreflightUpdate != null) {
813
+ return null;
814
+ }
815
+ if (options?.refreshSelectionAfterPreflight === true && react_native_1.Platform.OS === 'android') {
816
+ this.getSelection();
817
+ }
818
+ const update = this.parseAndNoteUpdate(mutate());
819
+ if (update) {
820
+ this._lastCommandPreflightUpdate = null;
821
+ }
662
822
  return update;
663
823
  }
664
824
  invalidateContentCaches() {