UNPKG

@eristack/spreadsheet-operator

Version:

Headless spreadsheet keyboard operator — active-grid scope, cell navigation, Excel-like Enter/Tab editing

74 lines (63 loc) • 4.45 kB
--- name: spreadsheet-operator-adapters description: > @eristack/spreadsheet-operator/react: SpreadsheetScopeProvider { config, onCommit({ gridId, address, fieldKey, value }), deactivateOnOutsidePointerDown } (window keydown + outside click deactivate), SpreadsheetTable { descriptor } (role grid), SpreadsheetNavCell { address } (role gridcell, data-active / data-editing / aria-selected, roving tabIndex), SpreadsheetTextCell { address, value, onCommit }, useSpreadsheetGrid + SpreadsheetGridIdProvider for div grids, useSpreadsheetCellEditor({ address, readValue, writeValue, onCommit, onCancel }) to bridge form-ui MoneyInput or a Select. Style via data-spreadsheet-active / data-active; no CSS ships. Use when wiring keyboard grids in React ERP screens. metadata: type: adapter library: "@eristack/spreadsheet-operator" library_version: "0.0.0" sources: - "eristack/business-libs:packages/ui/spreadsheet-operator/docs/getting-started.md" --- # Spreadsheet operator — React ```tsx import type { GridDescriptor } from "@eristack/spreadsheet-operator"; import { SpreadsheetNavCell, SpreadsheetScopeProvider, SpreadsheetTable, SpreadsheetTextCell, } from "@eristack/spreadsheet-operator/react"; const grid = useMemo<GridDescriptor>( () => ({ id: "lines", rowCount: rows.length, colCount: 2, cellAt: ({ col }) => ({ kind: "editable", fieldKey: col ? "unitPrice" : "quantity" }) }), [rows.length], ); <SpreadsheetScopeProvider onCommit={(e) => applyPatch({ [e.fieldKey!]: e.value })}> <SpreadsheetTable descriptor={grid}> <tbody> {rows.map((row, r) => ( <tr key={row.id}> {(["quantity", "unitPrice"] as const).map((f, c) => ( <SpreadsheetNavCell key={f} address={{ row: r, col: c }}> <SpreadsheetTextCell address={{ row: r, col: c }} value={row[f]} onCommit={(v) => update(row.id, f, v)} /> </SpreadsheetNavCell> ))} </tr> ))} </tbody> </SpreadsheetTable> </SpreadsheetScopeProvider> ``` | Export | Role | | --- | --- | | `SpreadsheetScopeProvider { config?, onCommit?, deactivateOnOutsidePointerDown? = true }` | One operator per document tab; window `keydown` while a grid is active (skips inputs outside the active grid); `pointerdown` outside all grids commits + deactivates | | `SpreadsheetTable { descriptor, className? }` | `<table role="grid" data-spreadsheet-grid data-spreadsheet-active?>` + click-to-activate + grid id context | | `SpreadsheetNavCell { address }` | `<td role="gridcell" data-spreadsheet-cell data-row data-col data-active? data-editing? aria-selected tabIndex>` | | `SpreadsheetTextCell { address, value, onCommit }` | `<span data-spreadsheet-display>` → uncontrolled `<input data-spreadsheet-editor>`; blur commits | | `useSpreadsheetGrid(descriptor)` → `{ gridProps, activeAddress, editing, isGridActive }` | Spread `gridProps` on a div / TanStack Table container; wrap children in `SpreadsheetGridIdProvider { gridId }` | | `useSpreadsheetCellEditor({ address, readValue, writeValue?, onCommit?, onCancel?, focus?, selectAll? })` → `{ editing, seed }` | Register any editor (form-ui `MoneyInput`, Radix Select, date picker) | | `useSpreadsheetScope()` → `{ operator, state, registerEditor }` | Toolbars, status bars, programmatic `operator.dispatch` | ## Checklist 1. One provider per screen/tab; every `SpreadsheetTable` inside gets a unique `descriptor.id`. 2. Memoize the descriptor on `rows.length`; derived columns `kind: "display"`. 3. Custom editors: let **Enter / Tab / Escape** bubble; `stopPropagation()` on other keys; keep the draft in a ref and return it from `readValue()`. 4. Select cells: open the listbox when `editing` flips true; commit on close. This package ships no Select. 5. CSS: `[data-spreadsheet-active] { outline: 2px solid hsl(var(--erista-color-primary)) }` and `[data-spreadsheet-cell][data-active] { box-shadow: inset 0 0 0 2px … }`. 6. Div grids: `useSpreadsheetGrid` + `SpreadsheetGridIdProvider`; cells need `data-spreadsheet-cell`, `data-row`, `data-col`. ## Do not - Nest unrelated inputs inside a `SpreadsheetTable` — keys typed there are treated as grid keys. - Put two providers with simultaneously active grids on one page. - Compute totals in `onCommit`; hand the string to `@eristack/line-grid` `applyPatch` / `@eristack/qups` `patchLine`. Demo: `examples/react` → `SpreadsheetOperatorDemo` (two grids sharing one provider).