@alaarab/ogrid-mcp 2.9.0 → 2.11.1
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.
- package/bundled-docs/api/README.md +7 -32
- package/bundled-docs/api/column-def.mdx +1 -1
- package/bundled-docs/api/components-column-chooser.mdx +0 -96
- package/bundled-docs/api/components-column-header-filter.mdx +1 -83
- package/bundled-docs/api/components-datagrid-table.mdx +0 -63
- package/bundled-docs/api/components-pagination-controls.mdx +0 -94
- package/bundled-docs/api/components-sidebar.mdx +0 -76
- package/bundled-docs/api/components-status-bar.mdx +0 -66
- package/bundled-docs/api/headless-hooks.mdx +410 -0
- package/bundled-docs/api/ogrid-props.mdx +1 -1
- package/bundled-docs/api/types.mdx +1 -1
- package/bundled-docs/features/cell-references.mdx +2 -116
- package/bundled-docs/features/column-chooser.mdx +0 -131
- package/bundled-docs/features/column-groups.mdx +1 -136
- package/bundled-docs/features/column-pinning.mdx +1 -108
- package/bundled-docs/features/column-reordering.mdx +1 -168
- package/bundled-docs/features/column-types.mdx +0 -71
- package/bundled-docs/features/context-menu.mdx +1 -93
- package/bundled-docs/features/csv-export.mdx +1 -104
- package/bundled-docs/features/editing.mdx +0 -149
- package/bundled-docs/features/filtering.mdx +0 -119
- package/bundled-docs/features/formulas.mdx +2 -102
- package/bundled-docs/features/grid-api.mdx +1 -108
- package/bundled-docs/features/keyboard-navigation.mdx +1 -74
- package/bundled-docs/features/mobile-touch.mdx +0 -71
- package/bundled-docs/features/pagination.mdx +0 -105
- package/bundled-docs/features/performance.mdx +0 -191
- package/bundled-docs/features/premium-inputs.mdx +0 -311
- package/bundled-docs/features/responsive-columns.mdx +1 -76
- package/bundled-docs/features/row-selection.mdx +2 -106
- package/bundled-docs/features/server-side-data.mdx +1 -129
- package/bundled-docs/features/sidebar.mdx +1 -78
- package/bundled-docs/features/sorting.mdx +2 -108
- package/bundled-docs/features/spreadsheet-selection.mdx +2 -79
- package/bundled-docs/features/status-bar.mdx +1 -72
- package/bundled-docs/features/toolbar.mdx +1 -75
- package/bundled-docs/features/virtual-scrolling.mdx +1 -304
- package/bundled-docs/getting-started/headless-or-component.mdx +112 -0
- package/bundled-docs/getting-started/installation.mdx +11 -141
- package/bundled-docs/getting-started/overview.mdx +15 -55
- package/bundled-docs/getting-started/quick-start.mdx +4 -279
- package/bundled-docs/guides/browser-support-matrix.mdx +6 -17
- package/bundled-docs/guides/mcp-live-testing.mdx +0 -82
- package/bundled-docs/guides/mcp.mdx +4 -4
- package/bundled-docs/guides/migration-from-ag-grid.mdx +2 -3
- package/bundled-docs/guides/theming.mdx +50 -23
- package/dist/esm/index.js +9 -39
- package/package.json +5 -5
- package/bundled-docs/api/js-api.mdx +0 -198
- package/bundled-docs/getting-started/vanilla-js.mdx +0 -218
- package/bundled-docs/guides/framework-showcase.mdx +0 -246
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 11
|
|
3
|
+
title: Headless hooks
|
|
4
|
+
description: API reference for useHeadlessGrid + the spreadsheet hook set (useInlineEdit, useRangeSelection, useFillHandle, useCellClipboard, useUndoRedo, useGridFocus).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Headless hooks
|
|
8
|
+
|
|
9
|
+
OGrid 2.9 ships seven framework-native hooks that expose grid logic without
|
|
10
|
+
imposing any chrome. Pair `useHeadlessGrid` with the spreadsheet hooks to
|
|
11
|
+
add inline edit, range selection, fill handle, clipboard, undo/redo, and
|
|
12
|
+
keyboard navigation to your own table markup (shadcn `<Table>`, plain HTML,
|
|
13
|
+
Material `<DataGrid>`, anything).
|
|
14
|
+
|
|
15
|
+
All seven hooks ship as React hooks:
|
|
16
|
+
|
|
17
|
+
- `import { useX } from '@alaarab/ogrid-react-radix'`
|
|
18
|
+
|
|
19
|
+
### Bundle-size tip — headless-only consumers
|
|
20
|
+
|
|
21
|
+
If your app **only** uses the hooks (no `<OGrid>` chrome), import from
|
|
22
|
+
`@alaarab/ogrid-react` instead of `@alaarab/ogrid-react-radix`. Same hooks,
|
|
23
|
+
no chrome CSS load (~40KB saved), and `sideEffects: false` lets your
|
|
24
|
+
bundler tree-shake aggressively:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// Headless-only — skip the radix package entirely
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Use `@alaarab/ogrid-react-radix` when you want both the headless hooks AND
|
|
31
|
+
the drop-in `<OGrid>` component.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## `useHeadlessGrid` — data layer
|
|
36
|
+
|
|
37
|
+
The foundation. Returns sort/filter/paginate state plus the current page
|
|
38
|
+
of rows. Supports `dataSource` for server-side mode.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
|
|
42
|
+
const grid = useHeadlessGrid({
|
|
43
|
+
columns,
|
|
44
|
+
data: rows,
|
|
45
|
+
getRowId: (row) => row.id,
|
|
46
|
+
initialSort: { field: 'name', direction: 'asc' },
|
|
47
|
+
initialPageSize: 25,
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Parameters
|
|
52
|
+
|
|
53
|
+
| Param | Type | Notes |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `columns` | `IColumnDef<T>[]` | Column definitions |
|
|
56
|
+
| `data` | `T[]` | Full dataset (client-side) or empty array (server-side) |
|
|
57
|
+
| `getRowId` | `(row: T) => RowId` | Stable row-identity extractor |
|
|
58
|
+
| `initialSort?` | `{ field, direction }` | Initial sort, omit for none |
|
|
59
|
+
| `initialFilters?` | `IFilters` | Initial filter values |
|
|
60
|
+
| `initialPage?` | `number` | Default `1` |
|
|
61
|
+
| `initialPageSize?` | `number` | Default `25` |
|
|
62
|
+
| `sort?`, `filters?`, `page?`, `pageSize?` | controlled overrides | Pass to put state under your control |
|
|
63
|
+
| `onSortChange?`, `onFiltersChange?`, `onPageChange?`, `onPageSizeChange?` | callbacks | Fired when state changes |
|
|
64
|
+
| `dataSource?` | `IDataSource<T>` | **React only.** Server-side mode |
|
|
65
|
+
| `workerSort?` | `boolean \| 'auto'` | **React only.** Worker-thread sort for large datasets |
|
|
66
|
+
|
|
67
|
+
### Returns
|
|
68
|
+
|
|
69
|
+
| Field | Description |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `rows` | Current page rows after sort + filter |
|
|
72
|
+
| `allFilteredRows` | Full filtered+sorted set (client mode) or current page (server mode) |
|
|
73
|
+
| `columns` | Resolved columns |
|
|
74
|
+
| `totalCount` | Post-filter total |
|
|
75
|
+
| `totalPages` | Total pages at current page size |
|
|
76
|
+
| `sort`, `filters`, `page`, `pageSize` | Current state |
|
|
77
|
+
| `setSort`, `toggleSort(columnId)`, `sortIndicator(columnId)` | Sort actions |
|
|
78
|
+
| `setFilters(filters)`, `setFilter(key, value)`, `hasActiveFilters` | Filter actions |
|
|
79
|
+
| `setPage(n)`, `setPageSize(n)` | Pagination |
|
|
80
|
+
| `getRowId`, `getCellValue(row, columnId)` | Row + cell helpers |
|
|
81
|
+
| `selectedRowIds`, `isRowSelected(row)`, `toggleRowSelection(row)`, `selectAllOnPage()`, `clearSelection()` | Minimal Set-based row selection |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## `useInlineEdit` — cell editing lifecycle
|
|
86
|
+
|
|
87
|
+
Manages start/commit/cancel for one cell at a time. Honors the column's
|
|
88
|
+
`editable` flag (boolean or per-row predicate) and validates new values
|
|
89
|
+
through `valueParser` — same flow `<OGrid>` uses internally.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
const edit = useInlineEdit({
|
|
93
|
+
columns,
|
|
94
|
+
getRowId: (row) => row.id,
|
|
95
|
+
onCellEdit: ({ item, columnId, oldValue, newValue }) =>
|
|
96
|
+
updateRow(item.id, { [columnId]: newValue }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// In render:
|
|
100
|
+
<TableCell onDoubleClick={() => edit.startEdit(row, col.columnId)}>
|
|
101
|
+
{edit.isEditing(row, col.columnId) ? (
|
|
102
|
+
<input autoFocus {...edit.getEditorProps(row, col.columnId)} />
|
|
103
|
+
) : (
|
|
104
|
+
String(grid.getCellValue(row, col.columnId))
|
|
105
|
+
)}
|
|
106
|
+
</TableCell>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Parameters
|
|
110
|
+
|
|
111
|
+
| Param | Type |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `columns` | `IColumnDef<T>[]` |
|
|
114
|
+
| `getRowId` | `(row: T) => RowId` |
|
|
115
|
+
| `onCellEdit` | `(event: { item, columnId, oldValue, newValue }) => void` |
|
|
116
|
+
| `isCellEditable?` | `(row, columnId) => boolean` (override per-row) |
|
|
117
|
+
|
|
118
|
+
### Returns
|
|
119
|
+
|
|
120
|
+
| Field | Description |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `editingCell` | `{ rowId, columnId } \| null` |
|
|
123
|
+
| `pendingValue`, `setPendingValue` | Buffered new value |
|
|
124
|
+
| `startEdit(row, columnId)` | Begin editing (no-op if not editable) |
|
|
125
|
+
| `commitEdit()` | Validate via `valueParser`, fire `onCellEdit` |
|
|
126
|
+
| `cancelEdit()` | Close without firing |
|
|
127
|
+
| `isEditing(row, columnId)` | `boolean` |
|
|
128
|
+
| `canEdit(row, columnId)` | `boolean` |
|
|
129
|
+
| `getEditorProps(row, columnId)` | Spread onto your input — `{ value, onChange, onBlur, onKeyDown }` (Enter commits, Escape cancels) |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## `useRangeSelection` — Excel-style range
|
|
134
|
+
|
|
135
|
+
Anchor + focus model. Foundation for `useFillHandle` and `useCellClipboard`.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
const range = useRangeSelection({
|
|
139
|
+
rowCount: grid.rows.length,
|
|
140
|
+
colCount: grid.columns.length,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Click + drag:
|
|
144
|
+
<TableCell
|
|
145
|
+
onMouseDown={(e) => e.shiftKey ? range.extendRange(rowIdx, colIdx) : range.startRange(rowIdx, colIdx)}
|
|
146
|
+
onMouseEnter={(e) => e.buttons === 1 && range.extendRange(rowIdx, colIdx)}
|
|
147
|
+
data-selected={range.isInRange(rowIdx, colIdx)}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Parameters
|
|
152
|
+
|
|
153
|
+
| Param | Type |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `rowCount` | `number` (visible row count) |
|
|
156
|
+
| `colCount` | `number` (visible column count) |
|
|
157
|
+
|
|
158
|
+
### Returns
|
|
159
|
+
|
|
160
|
+
| Field | Description |
|
|
161
|
+
|---|---|
|
|
162
|
+
| `range` | `ISelectionRange \| null` (normalized rectangular bounds) |
|
|
163
|
+
| `anchor`, `focus` | `CellCoord \| null` |
|
|
164
|
+
| `startRange(row, col)`, `extendRange(row, col)`, `setRange(range)`, `clearRange()` | State actions |
|
|
165
|
+
| `selectAll()` | Select every cell |
|
|
166
|
+
| `isInRange(row, col)` | `boolean` |
|
|
167
|
+
| `getRangeRows()`, `getRangeCells()` | Convenience extractors |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## `useFillHandle` — drag-to-fill
|
|
172
|
+
|
|
173
|
+
Excel-style fill via core's `applyFillValues`. Type-compatibility checks
|
|
174
|
+
prevent dragging incompatible types (text into a number column).
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
const fill = useFillHandle({
|
|
178
|
+
rangeSelection: range,
|
|
179
|
+
rows: grid.rows,
|
|
180
|
+
columns,
|
|
181
|
+
onFillCells: (events) => events.forEach(applyEdit),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Bottom-right of active range:
|
|
185
|
+
<div onMouseDown={fill.startFill} className="fill-handle-dot" />
|
|
186
|
+
|
|
187
|
+
// On every cell during drag:
|
|
188
|
+
<TableCell
|
|
189
|
+
onMouseEnter={() => fill.isFilling && fill.updateFill(rowIdx, colIdx)}
|
|
190
|
+
onMouseUp={fill.commitFill}
|
|
191
|
+
data-fill={fill.isInFillRange(rowIdx, colIdx)}
|
|
192
|
+
/>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Parameters
|
|
196
|
+
|
|
197
|
+
| Param | Type |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `rangeSelection` | `UseRangeSelectionResult` |
|
|
200
|
+
| `rows` | `T[]` (current page rows) |
|
|
201
|
+
| `columns` | `IColumnDef<T>[]` |
|
|
202
|
+
| `onFillCells` | `(events: ICellValueChangedEvent<T>[]) => void` |
|
|
203
|
+
|
|
204
|
+
### Returns
|
|
205
|
+
|
|
206
|
+
| Field | Description |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `fillTarget` | `CellCoord \| null` (current drag target) |
|
|
209
|
+
| `isFilling` | `boolean` |
|
|
210
|
+
| `fillRange` | `ISelectionRange \| null` (source range extended to target) |
|
|
211
|
+
| `startFill()`, `updateFill(row, col)`, `commitFill()`, `cancelFill()` | Lifecycle |
|
|
212
|
+
| `isInFillRange(row, col)` | `boolean` for highlighting |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## `useCellClipboard` — TSV copy/cut/paste
|
|
217
|
+
|
|
218
|
+
Round-trippable through Excel and Google Sheets. Honors `clipboardFormatter`
|
|
219
|
+
on copy and `valueParser` on paste validation.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
const clipboard = useCellClipboard({
|
|
223
|
+
rangeSelection: range,
|
|
224
|
+
rows: grid.rows,
|
|
225
|
+
columns,
|
|
226
|
+
onCellEdit: (events) => events.forEach(applyEdit),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
const handler = (e) => {
|
|
231
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
232
|
+
if (mod && e.key === 'c') clipboard.copyRange();
|
|
233
|
+
if (mod && e.key === 'x') clipboard.cutRange();
|
|
234
|
+
if (mod && e.key === 'v') clipboard.pasteRange();
|
|
235
|
+
};
|
|
236
|
+
document.addEventListener('keydown', handler);
|
|
237
|
+
return () => document.removeEventListener('keydown', handler);
|
|
238
|
+
}, [clipboard]);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Parameters
|
|
242
|
+
|
|
243
|
+
| Param | Type |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `rangeSelection` | `UseRangeSelectionResult` |
|
|
246
|
+
| `rows` | `T[]` |
|
|
247
|
+
| `columns` | `IColumnDef<T>[]` |
|
|
248
|
+
| `onCellEdit` | `(events: ICellValueChangedEvent<T>[]) => void` |
|
|
249
|
+
| `clipboard?` | `{ readText, writeText }` override (defaults to `navigator.clipboard`) |
|
|
250
|
+
|
|
251
|
+
### Returns
|
|
252
|
+
|
|
253
|
+
| Field | Description |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `copyRange()`, `cutRange()`, `pasteRange()` | Async actions |
|
|
256
|
+
| `canPaste` | `boolean` (feature detection) |
|
|
257
|
+
| `activeCutRange`, `activeCopyRange` | Marching-ants tracking |
|
|
258
|
+
| `clearClipboard()` | Dismiss markers (bind to Escape) |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## `useUndoRedo` — action history stack
|
|
263
|
+
|
|
264
|
+
Wraps your `onCellEdit` callback with an undo/redo history. Pair with
|
|
265
|
+
`useInlineEdit` / `useFillHandle` / `useCellClipboard` to get spreadsheet-
|
|
266
|
+
style undo across all of them for free.
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
const undo = useUndoRedo({ onCellValueChanged: applyEditToRows });
|
|
270
|
+
|
|
271
|
+
const edit = useInlineEdit({ columns, getRowId, onCellEdit: undo.onCellValueChanged });
|
|
272
|
+
const fill = useFillHandle({ ..., onFillCells: (events) => events.forEach(undo.onCellValueChanged) });
|
|
273
|
+
|
|
274
|
+
<button onClick={undo.undo} disabled={!undo.canUndo}>Undo</button>
|
|
275
|
+
<button onClick={undo.redo} disabled={!undo.canRedo}>Redo</button>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Parameters
|
|
279
|
+
|
|
280
|
+
| Param | Type |
|
|
281
|
+
|---|---|
|
|
282
|
+
| `onCellValueChanged` | `(event: ICellValueChangedEvent<T>) => void` |
|
|
283
|
+
| `maxUndoDepth?` | `number` (default `100`) |
|
|
284
|
+
|
|
285
|
+
### Returns
|
|
286
|
+
|
|
287
|
+
| Field | Description |
|
|
288
|
+
|---|---|
|
|
289
|
+
| `onCellValueChanged` | Wrapped callback — pass to other hooks instead of your raw handler |
|
|
290
|
+
| `undo()`, `redo()` | Actions |
|
|
291
|
+
| `canUndo`, `canRedo` | `boolean` flags |
|
|
292
|
+
| `beginBatch()`, `endBatch()` | Group multiple edits as one undo step |
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## `useGridFocus` — keyboard navigation
|
|
297
|
+
|
|
298
|
+
Arrow / Tab / Enter / Home / End / PageUp / PageDown. Pairs with
|
|
299
|
+
`useRangeSelection` so Shift+Arrow extends the active range.
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
const focus = useGridFocus({
|
|
303
|
+
rowCount: grid.rows.length,
|
|
304
|
+
colCount: grid.columns.length,
|
|
305
|
+
rangeSelection: range, // optional — enables Shift+Arrow extend
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
<div tabIndex={0} onKeyDown={focus.getKeyDownHandler()}>
|
|
309
|
+
{/* Render cells; highlight focus.activeCell */}
|
|
310
|
+
</div>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Parameters
|
|
314
|
+
|
|
315
|
+
| Param | Type |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `rowCount` | `number` |
|
|
318
|
+
| `colCount` | `number` |
|
|
319
|
+
| `pageSize?` | `number` (PageUp/Down step, default `10`) |
|
|
320
|
+
| `rangeSelection?` | `UseRangeSelectionResult` (optional, for Shift+Arrow extend) |
|
|
321
|
+
|
|
322
|
+
### Returns
|
|
323
|
+
|
|
324
|
+
| Field | Description |
|
|
325
|
+
|---|---|
|
|
326
|
+
| `activeCell`, `setActiveCell(cell)` | Current focus + setter |
|
|
327
|
+
| `moveUp/Down/Left/Right(n?)` | Programmatic movement |
|
|
328
|
+
| `moveToRowStart/RowEnd/Start/End()` | Navigation helpers |
|
|
329
|
+
| `getKeyDownHandler()` | Returns the handler to attach to your grid container's `onKeyDown` |
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## `useGridVirtualization` — windowed rendering
|
|
334
|
+
|
|
335
|
+
Headless row + column virtualization for large datasets. Tracks scroll
|
|
336
|
+
position on a consumer-owned container and reports which slice of rows/
|
|
337
|
+
columns to render with spacer offsets that preserve scroll geometry. Zero
|
|
338
|
+
external dependencies — uses core's pure compute helpers.
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
|
|
342
|
+
const grid = useHeadlessGrid({ columns, data, getRowId, initialPageSize: 10000 });
|
|
343
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
344
|
+
const virt = useGridVirtualization({
|
|
345
|
+
rowCount: grid.totalCount,
|
|
346
|
+
rowHeight: 36,
|
|
347
|
+
containerRef,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<div ref={containerRef} onScroll={virt.onScroll} style={{ height: 400, overflow: 'auto' }}>
|
|
352
|
+
<div style={{ height: virt.totalHeight, position: 'relative' }}>
|
|
353
|
+
<div style={{ transform: `translateY(${virt.rowRange.offsetTop}px)` }}>
|
|
354
|
+
{grid.rows.slice(virt.rowRange.startIndex, virt.rowRange.endIndex + 1).map((row) => (
|
|
355
|
+
<div key={grid.getRowId(row)} style={{ height: 36 }}>
|
|
356
|
+
{/* render row */}
|
|
357
|
+
</div>
|
|
358
|
+
))}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Parameters
|
|
366
|
+
|
|
367
|
+
| Param | Type | Notes |
|
|
368
|
+
|-------|------|-------|
|
|
369
|
+
| `rowCount` | `number` | Total rows in the data set. |
|
|
370
|
+
| `rowHeight` | `number` | Uniform row height in pixels. |
|
|
371
|
+
| `containerRef` | `RefObject<HTMLElement \| null>` | The scroll container — must have a fixed height + `overflow: auto`. |
|
|
372
|
+
| `overscan` | `number` | Extra rows above/below the visible window. Default `5`. |
|
|
373
|
+
| `enabled` | `boolean` | Disable virtualization entirely. Default `true`. |
|
|
374
|
+
| `threshold` | `number` | Below this row count, all rows render (avoids small-dataset artifacts). Default `100`. |
|
|
375
|
+
| `columnWidths` | `number[]` | Per-column widths (unpinned only) — enables column virt. Omit for row-only. |
|
|
376
|
+
| `columnOverscan` | `number` | Extra columns left/right of the viewport. Default `2`. |
|
|
377
|
+
|
|
378
|
+
### Returns
|
|
379
|
+
|
|
380
|
+
| Field | Type | Notes |
|
|
381
|
+
|-------|------|-------|
|
|
382
|
+
| `totalHeight` | `number` | Total scroll height (`rowCount × rowHeight`). |
|
|
383
|
+
| `rowRange` | `{ startIndex; endIndex; offsetTop; offsetBottom }` | Visible row slice + spacer pixels. |
|
|
384
|
+
| `columnRange` | `{ startIndex; endIndex; leftOffset; rightOffset } \| null` | Visible column slice (null when `columnWidths` omitted). |
|
|
385
|
+
| `scrollToIndex` | `(index, align?) => void` | Programmatically scroll a row into view (`'start' \| 'center' \| 'end'`). |
|
|
386
|
+
| `onScroll` | `() => void` | Attach to the container's `onScroll` prop. |
|
|
387
|
+
| `isActive` | `boolean` | True when virtualization is engaged (above threshold + enabled). |
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## All seven combined
|
|
392
|
+
|
|
393
|
+
The canonical demo combining every hook on a plain HTML table is the
|
|
394
|
+
[`SpreadsheetDemo` Storybook story](https://github.com/alaarab/ogrid/blob/main/packages/react-radix/src/OGrid/SpreadsheetDemo.stories.tsx) —
|
|
395
|
+
~200 lines you can copy as a starter template.
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
const grid = useHeadlessGrid({ columns, data, getRowId: (r) => r.id });
|
|
399
|
+
const range = useRangeSelection({ rowCount: grid.rows.length, colCount: grid.columns.length });
|
|
400
|
+
const undo = useUndoRedo({ onCellValueChanged: applyEditToRows });
|
|
401
|
+
const edit = useInlineEdit({ columns, getRowId, onCellEdit: undo.onCellValueChanged });
|
|
402
|
+
const fill = useFillHandle({ rangeSelection: range, rows: grid.rows, columns, onFillCells });
|
|
403
|
+
const clipboard = useCellClipboard({ rangeSelection: range, rows: grid.rows, columns, onCellEdit });
|
|
404
|
+
const focus = useGridFocus({ rowCount: grid.rows.length, colCount: grid.columns.length, rangeSelection: range });
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## See also
|
|
408
|
+
|
|
409
|
+
- [Headless or component?](/docs/getting-started/headless-or-component) — When to use these hooks vs `<OGrid>`
|
|
410
|
+
- [Theming](/docs/guides/theming) — Including the shadcn preset
|
|
@@ -5,7 +5,7 @@ title: OGrid Props
|
|
|
5
5
|
|
|
6
6
|
# OGrid Props
|
|
7
7
|
|
|
8
|
-
Complete reference for `IOGridProps<T>`, the props accepted by the `OGrid` component in all UI packages (`@alaarab/ogrid-react-radix`, `@alaarab/ogrid-react-fluent
|
|
8
|
+
Complete reference for `IOGridProps<T>`, the props accepted by the `OGrid` component in all UI packages (`@alaarab/ogrid-react-radix`, `@alaarab/ogrid-react-fluent`).
|
|
9
9
|
|
|
10
10
|
```typescript
|
|
11
11
|
```
|
|
@@ -5,7 +5,7 @@ title: Types
|
|
|
5
5
|
|
|
6
6
|
# Types
|
|
7
7
|
|
|
8
|
-
Reference for all shared types exported from `@alaarab/ogrid-core`. These types are also re-exported from all UI packages (`@alaarab/ogrid-react-radix`, `@alaarab/ogrid-react-fluent
|
|
8
|
+
Reference for all shared types exported from `@alaarab/ogrid-core`. These types are also re-exported from all UI packages (`@alaarab/ogrid-react-radix`, `@alaarab/ogrid-react-fluent`).
|
|
9
9
|
|
|
10
10
|
## RowId
|
|
11
11
|
|
|
@@ -13,14 +13,8 @@ Enable Excel-style cell references with a single prop. Column letter headers (A,
|
|
|
13
13
|
|
|
14
14
|
<CellReferencesDemo />
|
|
15
15
|
|
|
16
|
-
:::tip
|
|
17
|
-
The live demo above shows **
|
|
18
|
-
- **React** -- Radix UI default theme
|
|
19
|
-
- **Angular** -- Angular Material theme
|
|
20
|
-
- **Vue** -- Vuetify theme
|
|
21
|
-
- **JS** -- Vanilla JS default theme
|
|
22
|
-
|
|
23
|
-
Each framework renders with its native components, so the styling matches your design system.
|
|
16
|
+
:::tip UI styling
|
|
17
|
+
The live demo above shows **Radix UI** styling (lightweight default). The same feature renders through the Fluent UI package with its native components.
|
|
24
18
|
:::
|
|
25
19
|
|
|
26
20
|
## Quick Example
|
|
@@ -58,116 +52,8 @@ The `OGrid` component has the same props across all React UI packages. To switch
|
|
|
58
52
|
|
|
59
53
|
- **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
|
|
60
54
|
- **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` -- wrap in `<FluentProvider>`
|
|
61
|
-
- **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` -- wrap in `<ThemeProvider>`
|
|
62
55
|
:::
|
|
63
56
|
|
|
64
|
-
</TabItem>
|
|
65
|
-
<TabItem value="angular" label="Angular">
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
|
|
69
|
-
@Component({
|
|
70
|
-
standalone: true,
|
|
71
|
-
imports: [OGridComponent],
|
|
72
|
-
template: `<ogrid [props]="gridProps" />`
|
|
73
|
-
})
|
|
74
|
-
export class GridComponent {
|
|
75
|
-
gridProps = {
|
|
76
|
-
columns: [
|
|
77
|
-
{ columnId: 'name', name: 'Name' },
|
|
78
|
-
{ columnId: 'age', name: 'Age', type: 'numeric' },
|
|
79
|
-
{ columnId: 'email', name: 'Email' },
|
|
80
|
-
{ columnId: 'department', name: 'Department' },
|
|
81
|
-
{
|
|
82
|
-
columnId: 'salary', name: 'Salary', type: 'numeric',
|
|
83
|
-
valueFormatter: (v: unknown) => `$${Number(v).toLocaleString()}`,
|
|
84
|
-
},
|
|
85
|
-
] as IColumnDef<Person>[],
|
|
86
|
-
data: people,
|
|
87
|
-
getRowId: (item: Person) => item.id,
|
|
88
|
-
cellReferences: true,
|
|
89
|
-
defaultPageSize: 25,
|
|
90
|
-
entityLabelPlural: 'people',
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
:::tip Switching UI libraries
|
|
96
|
-
Same component API across Angular packages. To switch, just change the import:
|
|
97
|
-
|
|
98
|
-
- **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
|
|
99
|
-
- **Angular Material**: `from '@alaarab/ogrid-angular-material'`
|
|
100
|
-
- **PrimeNG**: `from '@alaarab/ogrid-angular-primeng'`
|
|
101
|
-
|
|
102
|
-
All components are standalone -- no NgModule required.
|
|
103
|
-
:::
|
|
104
|
-
|
|
105
|
-
</TabItem>
|
|
106
|
-
<TabItem value="vue" label="Vue">
|
|
107
|
-
|
|
108
|
-
```vue
|
|
109
|
-
<script setup lang="ts">
|
|
110
|
-
|
|
111
|
-
const columns: IColumnDef<Person>[] = [
|
|
112
|
-
{ columnId: 'name', name: 'Name' },
|
|
113
|
-
{ columnId: 'age', name: 'Age', type: 'numeric' },
|
|
114
|
-
{ columnId: 'email', name: 'Email' },
|
|
115
|
-
{ columnId: 'department', name: 'Department' },
|
|
116
|
-
{
|
|
117
|
-
columnId: 'salary', name: 'Salary', type: 'numeric',
|
|
118
|
-
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
|
|
119
|
-
},
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
const gridProps = {
|
|
123
|
-
columns,
|
|
124
|
-
data: people,
|
|
125
|
-
getRowId: (item) => item.id,
|
|
126
|
-
cellReferences: true,
|
|
127
|
-
defaultPageSize: 25,
|
|
128
|
-
entityLabelPlural: 'people',
|
|
129
|
-
};
|
|
130
|
-
</script>
|
|
131
|
-
|
|
132
|
-
<template>
|
|
133
|
-
<OGrid :gridProps="gridProps" />
|
|
134
|
-
</template>
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
:::tip Switching UI libraries
|
|
138
|
-
Same component API across Vue packages. To switch, just change the import:
|
|
139
|
-
|
|
140
|
-
- **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
|
|
141
|
-
- **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` -- wrap in `<v-app>` for theming
|
|
142
|
-
- **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
|
|
143
|
-
:::
|
|
144
|
-
|
|
145
|
-
</TabItem>
|
|
146
|
-
<TabItem value="js" label="Vanilla JS">
|
|
147
|
-
|
|
148
|
-
```js
|
|
149
|
-
|
|
150
|
-
const grid = new OGrid(document.getElementById('grid'), {
|
|
151
|
-
columns: [
|
|
152
|
-
{ columnId: 'name', name: 'Name' },
|
|
153
|
-
{ columnId: 'age', name: 'Age', type: 'numeric' },
|
|
154
|
-
{ columnId: 'email', name: 'Email' },
|
|
155
|
-
{ columnId: 'department', name: 'Department' },
|
|
156
|
-
{
|
|
157
|
-
columnId: 'salary',
|
|
158
|
-
name: 'Salary',
|
|
159
|
-
type: 'numeric',
|
|
160
|
-
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
data: people,
|
|
164
|
-
getRowId: (item) => item.id,
|
|
165
|
-
cellReferences: true,
|
|
166
|
-
pageSize: 25,
|
|
167
|
-
entityLabelPlural: 'people',
|
|
168
|
-
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
57
|
</TabItem>
|
|
172
58
|
</Tabs>
|
|
173
59
|
|
|
@@ -65,139 +65,8 @@ The `OGrid` component has the same props across all React UI packages. To switch
|
|
|
65
65
|
|
|
66
66
|
- **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
|
|
67
67
|
- **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` - wrap in `<FluentProvider>`
|
|
68
|
-
- **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` - wrap in `<ThemeProvider>`
|
|
69
68
|
:::
|
|
70
69
|
|
|
71
|
-
</TabItem>
|
|
72
|
-
<TabItem value="angular" label="Angular">
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
|
|
76
|
-
interface Person {
|
|
77
|
-
id: number;
|
|
78
|
-
name: string;
|
|
79
|
-
email: string;
|
|
80
|
-
age: number;
|
|
81
|
-
department: string;
|
|
82
|
-
salary: number;
|
|
83
|
-
startDate: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const columns: IColumnDef<Person>[] = [
|
|
87
|
-
{ columnId: 'name', name: 'Name', required: true },
|
|
88
|
-
{ columnId: 'email', name: 'Email' },
|
|
89
|
-
{ columnId: 'age', name: 'Age', type: 'numeric', defaultVisible: false },
|
|
90
|
-
{ columnId: 'department', name: 'Department' },
|
|
91
|
-
{
|
|
92
|
-
columnId: 'salary',
|
|
93
|
-
name: 'Salary',
|
|
94
|
-
type: 'numeric',
|
|
95
|
-
valueFormatter: (v: unknown) => `$${Number(v).toLocaleString()}`,
|
|
96
|
-
},
|
|
97
|
-
{ columnId: 'startDate', name: 'Start Date', defaultVisible: false },
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
@Component({
|
|
101
|
-
standalone: true,
|
|
102
|
-
imports: [OGridComponent],
|
|
103
|
-
template: `<ogrid [props]="gridProps" />`
|
|
104
|
-
})
|
|
105
|
-
export class GridComponent {
|
|
106
|
-
gridProps = {
|
|
107
|
-
columns,
|
|
108
|
-
data: people,
|
|
109
|
-
getRowId: (p: Person) => p.id,
|
|
110
|
-
defaultPageSize: 10,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
:::tip Switching UI libraries
|
|
116
|
-
Same component API across Angular packages. To switch, just change the import:
|
|
117
|
-
|
|
118
|
-
- **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
|
|
119
|
-
- **Angular Material**: `from '@alaarab/ogrid-angular-material'`
|
|
120
|
-
- **PrimeNG**: `from '@alaarab/ogrid-angular-primeng'`
|
|
121
|
-
|
|
122
|
-
All components are standalone - no NgModule required.
|
|
123
|
-
:::
|
|
124
|
-
|
|
125
|
-
</TabItem>
|
|
126
|
-
<TabItem value="vue" label="Vue">
|
|
127
|
-
|
|
128
|
-
```vue
|
|
129
|
-
<script setup lang="ts">
|
|
130
|
-
|
|
131
|
-
interface Person {
|
|
132
|
-
id: number;
|
|
133
|
-
name: string;
|
|
134
|
-
email: string;
|
|
135
|
-
age: number;
|
|
136
|
-
department: string;
|
|
137
|
-
salary: number;
|
|
138
|
-
startDate: string;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const columns: IColumnDef<Person>[] = [
|
|
142
|
-
{ columnId: 'name', name: 'Name', required: true },
|
|
143
|
-
{ columnId: 'email', name: 'Email' },
|
|
144
|
-
{ columnId: 'age', name: 'Age', type: 'numeric', defaultVisible: false },
|
|
145
|
-
{ columnId: 'department', name: 'Department' },
|
|
146
|
-
{
|
|
147
|
-
columnId: 'salary',
|
|
148
|
-
name: 'Salary',
|
|
149
|
-
type: 'numeric',
|
|
150
|
-
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
|
|
151
|
-
},
|
|
152
|
-
{ columnId: 'startDate', name: 'Start Date', defaultVisible: false },
|
|
153
|
-
];
|
|
154
|
-
|
|
155
|
-
const gridProps = {
|
|
156
|
-
columns,
|
|
157
|
-
data: people,
|
|
158
|
-
getRowId: (p: Person) => p.id,
|
|
159
|
-
defaultPageSize: 10,
|
|
160
|
-
};
|
|
161
|
-
</script>
|
|
162
|
-
|
|
163
|
-
<template>
|
|
164
|
-
<OGrid :gridProps="gridProps" />
|
|
165
|
-
</template>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
:::tip Switching UI libraries
|
|
169
|
-
Same component API across Vue packages. To switch, just change the import:
|
|
170
|
-
|
|
171
|
-
- **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
|
|
172
|
-
- **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` - wrap in `<v-app>` for theming
|
|
173
|
-
- **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
|
|
174
|
-
:::
|
|
175
|
-
|
|
176
|
-
</TabItem>
|
|
177
|
-
<TabItem value="js" label="Vanilla JS">
|
|
178
|
-
|
|
179
|
-
```js
|
|
180
|
-
|
|
181
|
-
const grid = new OGrid(document.getElementById('grid'), {
|
|
182
|
-
columns: [
|
|
183
|
-
{ columnId: 'name', name: 'Name', required: true },
|
|
184
|
-
{ columnId: 'email', name: 'Email' },
|
|
185
|
-
{ columnId: 'age', name: 'Age', type: 'numeric', defaultVisible: false },
|
|
186
|
-
{ columnId: 'department', name: 'Department' },
|
|
187
|
-
{
|
|
188
|
-
columnId: 'salary',
|
|
189
|
-
name: 'Salary',
|
|
190
|
-
type: 'numeric',
|
|
191
|
-
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
|
|
192
|
-
},
|
|
193
|
-
{ columnId: 'startDate', name: 'Start Date', defaultVisible: false },
|
|
194
|
-
],
|
|
195
|
-
data: people,
|
|
196
|
-
getRowId: (p) => p.id,
|
|
197
|
-
pageSize: 10,
|
|
198
|
-
});
|
|
199
|
-
```
|
|
200
|
-
|
|
201
70
|
</TabItem>
|
|
202
71
|
</Tabs>
|
|
203
72
|
|