export interface ComposerNativeEventSnapshot { readonly eventCount: number; readonly value: string; readonly selection: ComposerEditorSelection | null; } interface ComposerEditorSelection { readonly start: number; readonly end: number; } export function acknowledgeComposerNativeEvent( mostRecentEventCount: number, incomingEventCount: number, ): number | null { if (!Number.isSafeInteger(incomingEventCount) || incomingEventCount < mostRecentEventCount) { return null; } return incomingEventCount; } export function resolveComposerControlledEventCount( value: string, selection: ComposerEditorSelection | null, mostRecentEventCount: number, snapshots: ReadonlyArray, ): number { let newestValueEventCount: number | null = null; for (let index = snapshots.length - 1; index >= 0; index -= 1) { const snapshot = snapshots[index]; if (snapshot?.value !== value) continue; newestValueEventCount ??= snapshot.eventCount; if ( selection === null || (snapshot.selection?.start === selection.start && snapshot.selection.end === selection.end) ) { return snapshot.eventCount; } } // A value emitted by native paired with a different selection is an // intermediate React render. Keep it behind the native revision so it // cannot move the caret while newer keystrokes are being processed. if (newestValueEventCount !== null && mostRecentEventCount > 0) { return Math.min(newestValueEventCount, mostRecentEventCount - 1); } return mostRecentEventCount; } export function isComposerNativeEcho( value: string, selection: ComposerEditorSelection | null, eventCount: number, snapshots: ReadonlyArray, ): boolean { for (let index = snapshots.length - 1; index >= 0; index -= 1) { const snapshot = snapshots[index]; if ( snapshot !== undefined && snapshot.eventCount === eventCount && snapshot.value === value && (selection === null || (snapshot.selection?.start === selection.start && snapshot.selection.end === selection.end)) ) { return true; } } return false; } export function pruneAcknowledgedComposerNativeEvents( snapshots: ReadonlyArray, acknowledgedEventCount: number, ): ComposerNativeEventSnapshot[] { return snapshots.filter((snapshot) => snapshot.eventCount > acknowledgedEventCount); }