grid.on(evt, fn) subscribes to a grid event and returns an off() function. Call it to unsubscribe.
After a user types a value and presses Enter, the log shows edit-enter 1.
Event direction
change is the committed transaction stream. ChangeEvent.source is local for user or API output that a host may persist, and remote for already-persisted input that must not be enqueued again. Selection, search, renderer fallback, and other UI events remain observational.
Framework onGridChange / grid-change callbacks carry the same committed event. Adapter readiness is { grid, generation, reason }; reason distinguishes initial, input-reset, and renderer-reset. The imperative ref, exposed handle, or binding is assigned before readiness and cleared on replacement.
Grid events
Event Payload changeChangeEvent: { transaction; changes; commitReason; source: "local" | "remote"; epoch? }selection{ selection: Selection | null }scroll{ scrollTop; firstRow; lastRow; scrollLeft; firstVisibleColumn; lastVisibleColumn }; the visible-column fields are number | null.edit-begin{ addr: CellAddress }edit-commit{ addr: CellAddress; value: CellValue }searchSearchResult: { query: string; matches: CellAddress[]; active: number }command-state-change{ states }: every command's GridCommandState. See Host-owned command state .mutation-rejected{ issues: MutationIssue[] }active-sheet{ sheet: SheetId }, after the visible sheet changes (direct call or cross-sheet scroll).hyperlink-activateHyperlinkActivationEventtheme-change{ theme: Theme }, after setTheme or replaceTheme changes the base theme.renderer-fallback{ requested: "worker"; error: SheetwriteError }, once, when the worker renderer could not be constructed.datasource-error{ request; error: SheetwriteError }; request is the DataSourceRequest without its signal.export-error{ format: "csv" | "xlsx"; error: SheetwriteError }, when a built-in toolbar or context-menu export fails.
commitReason is one of edit-blur, edit-enter, edit-tab, edit-programmatic, paste, cut, clear, fill, structure, style, replace, undo, redo or api.
Sync coordinator events
SyncCoordinator has its own event stream. sync.on(fn) returns an unsubscribe function:
if ( event . type === "conflict" ) showConflict ( event . response ); if ( event . type === "reload-required" ) requestFreshSnapshot (); saveButton . onclick = () => void sync . sendNext ();
event.typePayload statestaterestoredpendingpersisting, pending, sendingmutationacknowledgedclientMutationId, version, duplicateconflictmutation, responseremote-appliedoperationreload-requiredexpectedVersion, receivedVersion, optional snapshotreloaded, recoveredserverVersion, pendingstorage-error, errorerror, optional clientMutationId
Edge cases and limits
Persist only change events with source: "local". Remote input is observable but never belongs in outgoing local persistence.
changes is empty for commits that only change metadata.
Hydration from a snapshot and datasource pages emit no user change event.
See also
const off: () => void const off : () => void const off : () => void const grid: Grid const grid : Grid const grid : Grid The imperative core grid this controller owns. API reference → Grid.on<"change">(evt: "change", fn: (e: ChangeEvent) => void): () => void Grid. on < "change" >(evt: "change" , fn: ( e : ChangeEvent ) => void ): () => void Grid. on < "change" >(evt: "change" , fn: ( e : ChangeEvent ) => void ): () => void transaction: Transaction let transaction : Transaction let transaction : Transaction Operations that actually committed after policy and bounds filtering. API reference → source: OperationSource let source : OperationSource let source : OperationSource Remote input is observable but never belongs in outgoing local persistence. API reference → commitReason: CommitReason let commitReason : CommitReason let commitReason : CommitReason What produced this commit — see CommitReason. API reference → var console: Console var console : Console var console : Console Console.log(...data: any[]): void (+1 overload) Console. log ( ... data: any[]): void ( + 1 overload) Console. log ( ... data: any[]): void ( + 1 overload) The console.log() static method outputs a message to the console. MDN Reference Transaction.patches: DocumentOp[] interface Transaction {
patches : DocumentOp [];
} interface Transaction {
patches : DocumentOp [];
} Ordered document operations submitted as one store commit. API reference → Array<DocumentOp>.length: number interface Array < DocumentOp > {
length : number ;
} interface Array < DocumentOp > {
length : number ;
} Gets or sets the length of the array. This is a number one higher than the highest index in the array. API reference → const sync: SyncCoordinator const sync : SyncCoordinator const sync : SyncCoordinator API reference → new SyncCoordinator(grid: Grid, adapter: PersistenceAdapter, options: SyncCoordinatorOptions): SyncCoordinator new SyncCoordinator (grid: Grid, adapter: PersistenceAdapter, options: SyncCoordinatorOptions): SyncCoordinator new SyncCoordinator (grid: Grid, adapter: PersistenceAdapter, options: SyncCoordinatorOptions): SyncCoordinator Deterministic, transport-neutral optimistic sync. Local rendering is never blocked: changes queue immediately, while hosts explicitly call sendNext or retry to perform network work. Example import { SyncCoordinator, type Grid, type PersistenceAdapter } from "@sheetwrite/core" ;
async function connect ( grid : Grid , adapter : PersistenceAdapter , documentId : string , serverVersion : number ) {
const sync = new SyncCoordinator (grid, adapter, { documentId, serverVersion });
await sync. ready ();
await sync. flush ();
return sync; // Call sync.destroy() before removing the Grid.
} See also PersistenceAdapter * See also SyncCoordinator.recoverConflict API reference → const adapter: PersistenceAdapter const adapter : PersistenceAdapter const adapter : PersistenceAdapter API reference → SyncCoordinatorOptions.documentId: string interface SyncCoordinatorOptions {
documentId : string ;
} interface SyncCoordinatorOptions {
documentId : string ;
} API reference → SyncCoordinatorOptions.serverVersion: number interface SyncCoordinatorOptions {
serverVersion : number ;
} interface SyncCoordinatorOptions {
serverVersion : number ;
} Last accepted contiguous remote server version. API reference → const loadedSnapshot: WorkbookSnapshot const loadedSnapshot : WorkbookSnapshot const loadedSnapshot : WorkbookSnapshot API reference → WorkbookSnapshot.version?: number | undefined interface WorkbookSnapshot {
version ?: number | undefined ;
} interface WorkbookSnapshot {
version ?: number | undefined ;
} API reference → SyncCoordinator.on(listener: SyncListener): () => void SyncCoordinator. on (listener: SyncListener): () => void SyncCoordinator. on (listener: SyncListener): () => void API reference → event: SyncCoordinatorEvent let event : SyncCoordinatorEvent let event : SyncCoordinatorEvent API reference → type: "conflict" | "state" | "restored" | "persisting" | "pending" | "sending" | "acknowledged" | "remote-applied" | "reload-required" | "reloaded" | "recovered" | "storage-error" | "error" let type :
| "conflict"
| "state"
| "restored"
| "persisting"
| "pending"
| "sending"
| "acknowledged"
| "remote-applied"
| "reload-required"
| "reloaded"
| "recovered"
| "storage-error"
| "error" let type :
| "conflict"
| "state"
| "restored"
| "persisting"
| "pending"
| "sending"
| "acknowledged"
| "remote-applied"
| "reload-required"
| "reloaded"
| "recovered"
| "storage-error"
| "error" Controls cell input parsing and default value formatting for this column. API reference → const showConflict: (conflict: unknown) => void const showConflict : ( conflict : unknown ) => void const showConflict : (
conflict : unknown ,
) => void event: {
type: "conflict";
mutation: SyncMutationRecord;
response: Extract<PersistenceCommitResponse, {
status: "conflict";
}>;
} let event : {
type : "conflict" ;
mutation : SyncMutationRecord ;
response : Extract <
PersistenceCommitResponse ,
{
status : "conflict" ;
}
>;
} let event : {
type : "conflict" ;
mutation : SyncMutationRecord ;
response : Extract <
PersistenceCommitResponse ,
{
status : "conflict" ;
}
>;
} response: {
status: "conflict";
currentVersion: number;
operationsSinceBase?: readonly VersionedOperation[];
snapshot?: WorkbookSnapshot;
} let response : {
status : "conflict" ;
currentVersion : number ;
operationsSinceBase ?: readonly VersionedOperation [];
snapshot ?: WorkbookSnapshot ;
} let response : {
status : "conflict" ;
currentVersion : number ;
operationsSinceBase ?: readonly VersionedOperation [];
snapshot ?: WorkbookSnapshot ;
} const requestFreshSnapshot: () => Promise<void> const requestFreshSnapshot : () => Promise < void > const requestFreshSnapshot : () => Promise < void > const saveButton: HTMLButtonElement const saveButton : HTMLButtonElement const saveButton : HTMLButtonElement GlobalEventHandlers.onclick: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null interface GlobalEventHandlers {
onclick :
(( this : GlobalEventHandlers , ev : PointerEvent ) => any ) | null ;
} interface GlobalEventHandlers {
onclick :
| ((
this : GlobalEventHandlers ,
ev : PointerEvent ,
) => any )
| null ;
} MDN Reference SyncCoordinator.sendNext(): Promise<PersistenceCommitResponse | null> SyncCoordinator. sendNext (): Promise < PersistenceCommitResponse | null > SyncCoordinator. sendNext (): Promise < PersistenceCommitResponse | null > SyncCoordinator.destroy(): void SyncCoordinator. destroy (): void SyncCoordinator. destroy (): void Runs immediately before a retained element is removed or replaced. API reference →