Docs Casual Sheets

SDK

Embed Casual Sheets into a React host via @casualoffice/sheets — the unified, cross-editor SDK contract.

Casual Sheets ships as one installable npm package, @casualoffice/sheets. It embeds a full spreadsheet editor (Univer Sheets under the hood) into a host app and exposes the unified SDK contract — the same shapes as @casualoffice/docs, so a host that learns one editor already knows the other. The host owns storage; the SDK stays storage-unaware and hands you content snapshots to persist.


Install and mount

npm i @casualoffice/sheets

Import the plugin CSS once at app boot:

import '@casualoffice/sheets/styles';

<CasualSheets> mounts a single workbook from an IWorkbookData snapshot and hands you the imperative API through onReady:

import { CasualSheets, type CasualSheetsAPI } from '@casualoffice/sheets';
import '@casualoffice/sheets/styles';

function Editor({ initialData }) {
  return (
    <CasualSheets
      initialData={initialData}
      onReady={(api: CasualSheetsAPI) => {/* stash the handle */}}
      onChange={(snapshot) => persist(snapshot)}
      style={{ width: '100%', height: '100%' }}
    />
  );
}

initialData is read once on mount. To swap workbooks, change the React key to remount, or call api.setContent(data). For hosts that prefer process isolation, CasualSheetsIframe renders the editor in an iframe and talks to it over the embed postMessage protocol.


Document mode

documentMode is the shared interaction mode. Sheets supports two values:

type DocumentMode = 'editing' | 'viewing';
  • 'editing' (default) — fully editable.
  • 'viewing' — read-only.

It is reactive — flipping the prop re-applies via api.setDocumentMode. The deprecated readOnly boolean maps to 'viewing' only when documentMode is unset; documentMode always wins.


Load & save

The host owns persistence. Read and replace content via the handle:

MethodPurpose
getContent()Current workbook as IWorkbookData (or null pre-mount).
setContent(data)Replace the workbook with a new snapshot; clears dirty.
import(input)Parse an .xlsx ArrayBuffer/Uint8Array/Blob and load.
export()Serialize the workbook to an .xlsx Blob.

import/export are the canonical cross-editor aliases of the format-specific importXlsx/exportXlsx. getSnapshot/loadSnapshot are deprecated aliases of getContent/setContent. The SDK also fires onSave on Ctrl/Cmd+S and onExit once with the final snapshot on unmount.


Events

Every canonical event is available two ways: as an on* prop on <CasualSheets> and via api.on(name, handler) / api.off(name, handler) on the handle. api.on returns an unsubscribe function; 'ready' is sticky.

Event / propPayload
ready / onReady(api: CasualSheetsAPI)
change / onChange(snapshot: IWorkbookData)
selectionChange / onSelectionChange(selection: RangeRef | null)
save / onSave(snapshot: IWorkbookData)
error / onError(error: Error)
dirtyChange / onDirtyChange(dirty: boolean)

onChange is debounced (default 400 ms; tune with onChangeDebounceMs) and captures programmatic edits too. dirtyChange flips true on the first edit since the last load/save and false on save / setContent / import.

const off = api.on('selectionChange', (sel) => updateStatusBar(sel));
off();

Imperative API (CasualSheetsAPI)

The handle from onReady is the stable, semver-covered integration surface.

MethodPurpose
getContent()Current IWorkbookData snapshot, or null.
setContent(data)Replace the workbook; clears the dirty flag.
import(input) / importXlsx(input)Load an .xlsx (ArrayBuffer / Uint8Array / Blob).
export() / exportXlsx()Serialize the workbook to an .xlsx Blob.
getSelection()Active selection as a RangeRef, or null.
focus()Move keyboard focus to the active workbook.
on(name, handler) / off(...)Subscribe / unsubscribe to an event.
executeCommand(id, params?)Dispatch a Univer command; resolves to its boolean result.
executeCommands(steps)Replay a sequence of recorded command/mutation steps.
setDocumentMode(mode) / getDocumentMode()Switch / read 'editing' vs 'viewing'.
univerRaw FUniver facade — escape hatch, not covered by semver.

getSnapshot / loadSnapshot remain as deprecated aliases.

await api.executeCommand('sheet.command.set-range-values', { value: 42 });
const range = api.getSelection(); // { unitId, sheetId, range }

Chrome & features

By default the SDK renders a bare grid (chrome="none") so the host brings its own shell. Pass chrome="minimal" or chrome="full" to get the built-in Office shell (menu bar, formatting toolbar, formula bar, sheet tab strip, status bar).

<CasualSheets initialData={data} chrome="full" features={{ merge: false }} />

features?: Record<string, boolean> toggles individual chrome controls. false hides the control and blocks its command; omitted keys default to enabled. Only applies when chrome is shown.

extensions?: ChromeExtensions lets a host append custom toolbar items, menu entries, dialogs, and side panels on top of the built-in chrome. Every extension is handed the live CasualSheetsAPI.


Collaboration by config

Pass a collab prop to join a real-time room; the SDK wires Yjs/Hocuspocus itself once the editor is ready and detaches on unmount. Omit it for a single-user editor.

<CasualSheets
  initialData={data}
  collab={{
    server: 'wss://your-host/yjs',
    room: 'workbook-42',
    token: authToken,             // optional; defaults to 'anon'
    role: 'write',                // 'view' | 'write'; default 'write'
    onStatus: (s) => setStatus(s), // 'connecting' | 'live' | 'offline'
  }}
/>

Yjs/Hocuspocus is the realtime transport only — the authoritative document is still saved by the host via the save/exit contract; collab does not turn the SDK into a store.


The AI assistant

Pass an ai prop to mount a task-pane surface beside the grid. The SDK owns the prop contract and layout slot; the host supplies the panel body via ai.render.

import { CasualSheets, createSheetsAiTransport } from '@casualoffice/sheets';

<CasualSheets
  initialData={data}
  ai={{
    enabled: true,
    transport: createSheetsAiTransport(), // desktop-native → collab → browser-direct
    render: (ctx) => <YourAiPanel {...ctx} />,
    onAction: (action) => trackAiAction(action),
  }}
/>;

enabled is reactive. createSheetsAiTransport() picks the transport for the environment: desktop-native inside the desktop shell, collab-proxied when a collab WS URL is available, else browser-direct (bring-your-own key).


Appearance

appearance?: 'light' | 'dark' is reactive — flipping it re-themes the live editor. The imperative equivalent is api.setTheme('light' | 'dark').