@alaarab/ogrid-mcp 2.15.1 → 2.15.2
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/README.md +60 -3
- package/bundled-docs/api/headless-hooks.mdx +4 -5
- package/bundled-docs/api/ogrid-props.mdx +29 -0
- package/bundled-docs/features/column-types.mdx +3 -0
- package/bundled-docs/features/mobile-touch.mdx +5 -0
- package/bundled-docs/features/multi-sheet.mdx +91 -0
- package/bundled-docs/features/performance.mdx +3 -0
- package/bundled-docs/features/premium-inputs.mdx +3 -0
- package/bundled-docs/features/responsive-columns.mdx +3 -0
- package/bundled-docs/features/xlsx-import.mdx +36 -5
- package/bundled-docs/getting-started/headless-or-component.mdx +3 -3
- package/bundled-docs/getting-started/installation.mdx +2 -7
- package/bundled-docs/getting-started/overview.mdx +1 -1
- package/bundled-docs/guides/theming.mdx +3 -3
- package/dist/esm/bridge-client.js +7 -7
- package/dist/esm/index.js +47 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @alaarab/ogrid-mcp
|
|
2
2
|
|
|
3
|
-
MCP server that lets AI editors search and retrieve OGrid documentation
|
|
3
|
+
MCP server that lets AI editors (Claude Code, Cursor, VS Code Copilot, etc.) search and retrieve OGrid documentation — and, optionally, inspect and drive live OGrid instances in your running app through a local testing bridge.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -17,7 +17,64 @@ Add to your editor's MCP configuration:
|
|
|
17
17
|
Or run directly:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx @alaarab/ogrid-mcp
|
|
20
|
+
npx @alaarab/ogrid-mcp # docs server (stdio)
|
|
21
|
+
npx @alaarab/ogrid-mcp --bridge # docs server + live testing bridge on port 7890
|
|
22
|
+
npx @alaarab/ogrid-mcp --version # print version
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
## Tools
|
|
26
|
+
|
|
27
|
+
Documentation tools (always available):
|
|
28
|
+
|
|
29
|
+
| Tool | Description |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `search_docs` | Search OGrid documentation by keyword. Returns matching docs with title, description, and content excerpt. |
|
|
32
|
+
| `list_docs` | List available documentation pages, optionally filtered by category (`features`, `getting-started`, `guides`, `api`). |
|
|
33
|
+
| `get_docs` | Get the full content of a documentation page by path (e.g. `features/sorting`, `api/column-def`). |
|
|
34
|
+
| `get_code_example` | Find code examples from the docs matching a query, optionally filtered by framework. |
|
|
35
|
+
| `detect_version` | Detect which OGrid version and framework a project uses by reading its `package.json`. |
|
|
36
|
+
|
|
37
|
+
Live-bridge tools (available when the bridge is enabled):
|
|
38
|
+
|
|
39
|
+
| Tool | Description |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `list_grids` | List OGrid instances currently connected to the bridge: grid IDs, row counts, page info, last-seen timestamps. |
|
|
42
|
+
| `get_grid_state` | Get a connected grid's current state: displayed rows, columns, sort, filters, pagination, and selection. |
|
|
43
|
+
| `send_grid_command` | Send a command to a connected grid (sort, filter, paginate, edit a cell, …) and wait for the result. |
|
|
44
|
+
|
|
45
|
+
## Resources
|
|
46
|
+
|
|
47
|
+
| URI | Description |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `ogrid://quick-reference` | Key props, install commands, and common patterns. |
|
|
50
|
+
| `ogrid://migration-guide` | Full migration guide from AG Grid with side-by-side API mapping. |
|
|
51
|
+
| `ogrid://docs/{path}` | Any documentation page by path. |
|
|
52
|
+
|
|
53
|
+
## Live testing bridge
|
|
54
|
+
|
|
55
|
+
The bridge lets an AI assistant observe and drive real OGrid instances in your running app — useful for agentic testing and debugging.
|
|
56
|
+
|
|
57
|
+
1. Start the server with the bridge enabled: `npx @alaarab/ogrid-mcp --bridge` (or set `OGRID_BRIDGE_PORT` to pick a port; `--bridge` defaults to `7890`).
|
|
58
|
+
2. In your app, connect a grid to the bridge:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { connectGridToBridge } from '@alaarab/ogrid-mcp/bridge-client';
|
|
62
|
+
|
|
63
|
+
const connection = connectGridToBridge({
|
|
64
|
+
gridId: 'employees',
|
|
65
|
+
getData: () => rows,
|
|
66
|
+
getColumns: () => columns,
|
|
67
|
+
api: gridApiRef.current, // optional: enables send_grid_command actions
|
|
68
|
+
// bridgeUrl: 'http://localhost:7890' (default), pollIntervalMs: 500 (default)
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// later: connection.disconnect();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
3. The assistant can now call `list_grids`, `get_grid_state`, and `send_grid_command` against your live app.
|
|
75
|
+
|
|
76
|
+
The bridge client polls the local bridge over HTTP and never talks to anything but `bridgeUrl`.
|
|
77
|
+
|
|
78
|
+
## Documentation
|
|
79
|
+
|
|
80
|
+
See the [OGrid docs](https://alaarab.github.io/ogrid/) — in particular the [MCP guide](https://alaarab.github.io/ogrid/docs/guides/mcp) and [MCP live testing guide](https://alaarab.github.io/ogrid/docs/guides/mcp-live-testing).
|
|
@@ -6,11 +6,10 @@ description: API reference for useHeadlessGrid + the spreadsheet hook set (useIn
|
|
|
6
6
|
|
|
7
7
|
# Headless hooks
|
|
8
8
|
|
|
9
|
-
OGrid
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Fluent `<DataGrid>`, anything).
|
|
9
|
+
OGrid exposes hooks that provide the grid logic without imposing any UI.
|
|
10
|
+
Pair `useHeadlessGrid` with the spreadsheet hooks to add inline edit, range
|
|
11
|
+
selection, fill handle, clipboard, undo/redo, and keyboard navigation to your
|
|
12
|
+
own table markup (shadcn `<Table>`, plain HTML, Fluent `<DataGrid>`, anything).
|
|
14
13
|
|
|
15
14
|
All seven hooks ship as React hooks:
|
|
16
15
|
|
|
@@ -33,6 +33,7 @@ Complete reference for `IOGridProps<T>`, the props accepted by the `OGrid` compo
|
|
|
33
33
|
| `onSortChange` | `(sort: { field: string; direction: 'asc' \| 'desc' }) => void` | `undefined` | Callback fired when the user changes the sort. Required for controlled sort mode. |
|
|
34
34
|
| `defaultSortBy` | `string` | `undefined` | Column ID to sort by initially in uncontrolled mode. |
|
|
35
35
|
| `defaultSortDirection` | `'asc' \| 'desc'` | `undefined` | Initial sort direction in uncontrolled mode. Used together with `defaultSortBy`. |
|
|
36
|
+
| `workerSort` | `boolean \| 'auto'` | `false` | Offload sorting to a Web Worker to keep the main thread responsive. `true` always uses the worker; `'auto'` uses it past ~5000 rows. Columns with a custom `compare` fall back to synchronous sort. |
|
|
36
37
|
|
|
37
38
|
## Filtering
|
|
38
39
|
|
|
@@ -61,6 +62,7 @@ Complete reference for `IOGridProps<T>`, the props accepted by the `OGrid` compo
|
|
|
61
62
|
| `columnOrder` | `string[]` | `undefined` | Controlled column order as an array of column IDs. |
|
|
62
63
|
| `onColumnOrderChange` | `(order: string[]) => void` | `undefined` | Callback fired when the user reorders columns. |
|
|
63
64
|
| `onColumnResized` | `(columnId: string, width: number) => void` | `undefined` | Callback fired when the user finishes resizing a column. |
|
|
65
|
+
| `responsiveColumns` | `boolean \| IResponsiveColumnsConfig` | `false` | Auto-hide columns by their `responsivePriority` as the container narrows. `true` uses default breakpoints (576/768/992/1200px); pass a config object for custom thresholds. |
|
|
64
66
|
|
|
65
67
|
## Editing
|
|
66
68
|
|
|
@@ -128,12 +130,39 @@ Complete reference for `IOGridProps<T>`, the props accepted by the `OGrid` compo
|
|
|
128
130
|
| `rowHeight` | `number` | `36` | Fixed row height in pixels. Affects layout and virtual scrolling row height calculation. |
|
|
129
131
|
| `density` | `'compact' \| 'normal' \| 'comfortable'` | `'normal'` | Cell spacing preset. Controls cell padding throughout the grid. |
|
|
130
132
|
| `showRowNumbers` | `boolean` | `false` | Show Excel-style row number column (1, 2, 3...) at the start of the grid. |
|
|
133
|
+
| `cellReferences` | `boolean` | `false` | Excel-style cell references: column-letter headers (A, B, C…), a row-number gutter, and a name box showing the active cell (e.g. "A1"). Implies `showRowNumbers`. |
|
|
131
134
|
| `className` | `string` | `undefined` | Additional CSS class name applied to the root grid element. |
|
|
132
135
|
| `toolbar` | `ReactNode` | `undefined` | Custom toolbar content rendered in the left side of the primary toolbar strip. |
|
|
133
136
|
| `toolbarBelow` | `ReactNode` | `undefined` | Secondary toolbar row rendered below the primary toolbar. Use for active filter chips, breadcrumbs, or other contextual controls. |
|
|
134
137
|
| `entityLabelPlural` | `string` | `undefined` | Plural label for the entity type (e.g., `"projects"`, `"users"`). Used in status bar text and empty state messages. |
|
|
135
138
|
| `emptyState` | `{ message?: ReactNode; render?: () => ReactNode }` | `undefined` | Custom empty state configuration. Provide `message` for a simple text override, or `render` for a fully custom empty state component. |
|
|
136
139
|
|
|
140
|
+
## Formulas
|
|
141
|
+
|
|
142
|
+
| Name | Type | Default | Description |
|
|
143
|
+
|------|------|---------|-------------|
|
|
144
|
+
| `formulas` | `boolean` | `false` | Enable Excel-like formulas. When on, cells starting with `=` are evaluated by the built-in formula engine. |
|
|
145
|
+
| `initialFormulas` | `Array<{ col: number; row: number; formula: string }>` | `undefined` | Formulas to load when the engine initializes. |
|
|
146
|
+
| `onFormulaRecalc` | `(result: IRecalcResult) => void` | `undefined` | Called when a recalculation updates cell values (e.g. a cascade from an edited cell). |
|
|
147
|
+
| `formulaFunctions` | `Record<string, IFormulaFunction>` | `undefined` | Custom functions to register with the formula engine. |
|
|
148
|
+
| `namedRanges` | `Record<string, string>` | `undefined` | Named ranges for formulas, mapping a name to a cell/range ref (e.g. `{ Revenue: 'A1:A10' }`). |
|
|
149
|
+
| `sheets` | `Record<string, IGridDataAccessor>` | `undefined` | Accessors for cross-sheet formula references (e.g. `{ Sheet2: accessor }`). |
|
|
150
|
+
|
|
151
|
+
See [Formulas](../features/formulas) for the full guide.
|
|
152
|
+
|
|
153
|
+
## Multi-Sheet
|
|
154
|
+
|
|
155
|
+
Excel-style sheet tabs along the bottom of the grid.
|
|
156
|
+
|
|
157
|
+
| Name | Type | Default | Description |
|
|
158
|
+
|------|------|---------|-------------|
|
|
159
|
+
| `sheetDefs` | `ISheetDef[]` | `undefined` | Sheet definitions. When set, renders a bottom tab bar (the `SheetTabs` component). |
|
|
160
|
+
| `activeSheet` | `string` | `undefined` | The currently active sheet id. |
|
|
161
|
+
| `onSheetChange` | `(sheetId: string) => void` | `undefined` | Called when the user switches sheets. |
|
|
162
|
+
| `onSheetAdd` | `() => void` | `undefined` | Called when the user clicks the add-sheet button. |
|
|
163
|
+
|
|
164
|
+
See [Multi-Sheet](../features/multi-sheet).
|
|
165
|
+
|
|
137
166
|
## Callbacks
|
|
138
167
|
|
|
139
168
|
| Name | Type | Default | Description |
|
|
@@ -5,10 +5,13 @@ description: Built-in column types for common data patterns - text, numeric, d
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# Column Types
|
|
9
10
|
|
|
10
11
|
OGrid provides built-in column types that automatically configure alignment, display formatting, cell editors, and filter types. Set `type` on a column definition to opt in.
|
|
11
12
|
|
|
13
|
+
<ColumnTypesDemo />
|
|
14
|
+
|
|
12
15
|
## Available Types
|
|
13
16
|
|
|
14
17
|
| Type | Alignment | Default Editor | Default Filter | Display Format |
|
|
@@ -5,10 +5,15 @@ description: Touch-friendly interactions using the Pointer Events API for cell s
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# Mobile Touch Support
|
|
9
10
|
|
|
10
11
|
OGrid uses the [Pointer Events API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) for all drag interactions, providing unified input handling across mouse, touch, and pen devices. All interactions - cell drag-selection, fill handle drag-to-fill, column resize, and column reorder - work on touch devices out of the box with no additional configuration.
|
|
11
12
|
|
|
13
|
+
<MobileTouchDemo />
|
|
14
|
+
|
|
15
|
+
The demo above uses the exact same pointer gestures on every device — try it with a mouse here, or open this page on a phone or tablet: the fill and resize handles automatically enlarge under `@media (pointer: coarse)` for finger-sized targets.
|
|
16
|
+
|
|
12
17
|
## How It Works
|
|
13
18
|
|
|
14
19
|
### Pointer Events API
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 6
|
|
3
|
+
title: Multi-Sheet
|
|
4
|
+
description: Excel-style sheet tabs — render a bottom tab bar and switch between multiple grids.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Multi-Sheet
|
|
9
|
+
|
|
10
|
+
OGrid can render an Excel-style **sheet tab bar** along the bottom of the grid so users can switch between multiple sheets. Provide `sheetDefs`, track the `activeSheet`, and swap the grid's `data` (and `columns`, if they differ) when the active sheet changes.
|
|
11
|
+
|
|
12
|
+
<MultiSheetDemo />
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
| Name | Type | Description |
|
|
17
|
+
|------|------|-------------|
|
|
18
|
+
| `sheetDefs` | `ISheetDef[]` | Sheet definitions. When set, the grid renders the bottom tab bar. |
|
|
19
|
+
| `activeSheet` | `string` | The id of the currently active sheet. |
|
|
20
|
+
| `onSheetChange` | `(sheetId: string) => void` | Called when the user clicks a different tab. |
|
|
21
|
+
| `onSheetAdd` | `() => void` | Called when the user clicks the **+** button. Omit to hide it. |
|
|
22
|
+
|
|
23
|
+
`ISheetDef` is a small shape from `@alaarab/ogrid-core`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
interface ISheetDef {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
color?: string; // optional tab color (any CSS color)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Example
|
|
34
|
+
|
|
35
|
+
The grid is controlled: you own `activeSheet` and pick which data to show.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
|
|
39
|
+
const sheets = [
|
|
40
|
+
{ id: "q1", name: "Q1" },
|
|
41
|
+
{ id: "q2", name: "Q2" },
|
|
42
|
+
{ id: "fy", name: "Full Year", color: "#217346" },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const dataBySheet = {
|
|
46
|
+
q1: q1Rows,
|
|
47
|
+
q2: q2Rows,
|
|
48
|
+
fy: fyRows,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function Workbook() {
|
|
52
|
+
const [activeSheet, setActiveSheet] = useState("q1");
|
|
53
|
+
return (
|
|
54
|
+
<OGrid
|
|
55
|
+
columns={columns}
|
|
56
|
+
data={dataBySheet[activeSheet]}
|
|
57
|
+
getRowId={(r) => r.id}
|
|
58
|
+
sheetDefs={sheets}
|
|
59
|
+
activeSheet={activeSheet}
|
|
60
|
+
onSheetChange={setActiveSheet}
|
|
61
|
+
onSheetAdd={() => {/* add a sheet to your own state */}}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Cross-sheet formulas
|
|
68
|
+
|
|
69
|
+
When [formulas](./formulas) are enabled, the `sheets` prop registers other sheets' data as accessors so a formula can reference another sheet (e.g. `=Q2!A1`):
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<OGrid
|
|
73
|
+
/* ...sheet props as above... */
|
|
74
|
+
formulas
|
|
75
|
+
sheets={{ Q2: q2Accessor, FY: fyAccessor }}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Standalone tab bar
|
|
80
|
+
|
|
81
|
+
The tab bar is also exported on its own as `SheetTabs` from `@alaarab/ogrid-react`, if you want to render it outside the grid:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
|
|
85
|
+
<SheetTabs
|
|
86
|
+
sheets={sheets}
|
|
87
|
+
activeSheet={activeSheet}
|
|
88
|
+
onSheetChange={setActiveSheet}
|
|
89
|
+
onSheetAdd={addSheet}
|
|
90
|
+
/>;
|
|
91
|
+
```
|
|
@@ -5,10 +5,13 @@ description: Automatic CSS containment, opt-in Web Worker sort/filter, and colum
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# Performance
|
|
9
10
|
|
|
10
11
|
A grid that handles 100 rows fine but chokes at 50,000 isn't production-ready. OGrid ships with three complementary features to handle large datasets without framework-level heroics:
|
|
11
12
|
|
|
13
|
+
<PerformanceDemo />
|
|
14
|
+
|
|
12
15
|
| Feature | Opt-in? | What it does |
|
|
13
16
|
|---------|---------|---------|
|
|
14
17
|
| **CSS Containment** | No (automatic) | Browser skips layout and paint for off-screen cells |
|
|
@@ -5,10 +5,13 @@ description: Optional premium cell editors with zero bundle impact when not inst
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# Premium Inputs
|
|
9
10
|
|
|
10
11
|
OGrid's premium inputs are optional cell editor components distributed in a separate package. They are fully tree-shakeable and have **zero bundle impact** when not installed -- your grid stays lightweight until you explicitly opt in.
|
|
11
12
|
|
|
13
|
+
<PremiumInputsDemo />
|
|
14
|
+
|
|
12
15
|
## Installation
|
|
13
16
|
|
|
14
17
|
<Tabs groupId="framework">
|
|
@@ -5,10 +5,13 @@ description: Automatically hide columns based on container width using responsiv
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# Responsive Columns
|
|
9
10
|
|
|
10
11
|
OGrid can automatically hide lower-priority columns when the grid container becomes narrow, keeping the most important data visible on smaller screens. Columns are progressively hidden based on `responsivePriority` values and configurable width breakpoints.
|
|
11
12
|
|
|
13
|
+
<ResponsiveColumnsDemo />
|
|
14
|
+
|
|
12
15
|
## How It Works
|
|
13
16
|
|
|
14
17
|
1. Assign `responsivePriority` to columns (0 = highest priority, always visible).
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
sidebar_position: 16
|
|
3
|
-
title: XLSX Import
|
|
4
|
-
description: Drop an .xlsx file into a fully featured OGrid —
|
|
3
|
+
title: XLSX Import & Export
|
|
4
|
+
description: Drop an .xlsx file into a fully featured OGrid — and export grid data back out as a real workbook
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
# XLSX Import
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
# XLSX Import & Export
|
|
10
|
+
|
|
11
|
+
Render any `.xlsx`, CSV, or TSV blob as a fully featured OGrid — and export grid data back out as a real `.xlsx` workbook. Multi-sheet workbooks get tabbed navigation, formulas evaluate live, and Excel-style cell references (A1, B2…) are on by default.
|
|
12
|
+
|
|
13
|
+
<XlsxImportDemo />
|
|
11
14
|
|
|
12
15
|
Two packages ship this:
|
|
13
16
|
|
|
@@ -64,7 +67,7 @@ function SingleSheet({ file }: { file: Blob }) {
|
|
|
64
67
|
|
|
65
68
|
### Format support
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
Supported formats: `.xlsx`, `.csv`, `.tsv` (backed by ExcelJS).
|
|
68
71
|
|
|
69
72
|
## Browser bundle (no bundler)
|
|
70
73
|
|
|
@@ -117,6 +120,34 @@ Returns an unmount function. Always call it before removing the host node — Re
|
|
|
117
120
|
| You want tree-shaking and a single dependency graph. | You don't have (and don't want) a JS bundler. |
|
|
118
121
|
| You're integrating into an existing React tree. | You want one `<script type="module">` import. |
|
|
119
122
|
|
|
123
|
+
## Exporting
|
|
124
|
+
|
|
125
|
+
`exportToXlsx` mirrors [`exportToCsv`](./csv-export)'s call shape — pass rows, lightweight column defs, a value accessor, and a filename:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
|
|
129
|
+
await exportToXlsx(
|
|
130
|
+
rows,
|
|
131
|
+
[
|
|
132
|
+
{ columnId: 'name', name: 'Name' },
|
|
133
|
+
{ columnId: 'salary', name: 'Salary' },
|
|
134
|
+
],
|
|
135
|
+
(item, columnId) => item[columnId],
|
|
136
|
+
'people.xlsx',
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Values export as native Excel types: numbers, dates, and booleans round-trip without stringification. To emit **live formulas** (Excel recalculates them on open), pass the same `{col, row, formula}` array `sheetToGridData` produced on import:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
await exportToXlsx(rows, columns, getValue, 'sheet.xlsx', {
|
|
144
|
+
sheetName: 'Data',
|
|
145
|
+
formulas: initialFormulas, // e.g. from the imported workbook
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For full control (multi-sheet assembly, styling), build the workbook yourself with `workbookFromGridData(rows, columns, getValue, options)` — it returns an `ExcelJS.Workbook` you can add sheets to before serializing with `xlsxBlobFromWorkbook(wb)`.
|
|
150
|
+
|
|
120
151
|
## Related
|
|
121
152
|
|
|
122
153
|
- [Formulas](./formulas) — the engine that evaluates `=SUM(A1:A5)` cells lifted out of the workbook
|
|
@@ -81,10 +81,10 @@ The full integration code is the
|
|
|
81
81
|
|
|
82
82
|
## Both at once — yes, in the same app
|
|
83
83
|
|
|
84
|
-
Mix freely. A
|
|
84
|
+
Mix freely. A common pattern:
|
|
85
85
|
|
|
86
|
-
-
|
|
87
|
-
-
|
|
86
|
+
- Standard admin / list pages → `<OGrid>` (fast to build, sensible default UI)
|
|
87
|
+
- Pages where the table is the product → the hooks (full control over the markup)
|
|
88
88
|
|
|
89
89
|
Same package import. Same theme tokens. Same TypeScript types. The
|
|
90
90
|
spreadsheet features (inline edit, fill handle, clipboard, undo) work
|
|
@@ -34,14 +34,9 @@ npm install @alaarab/ogrid-react-fluent @fluentui/react-components
|
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## Other adapters
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **`@alaarab/ogrid-react-material`** — frozen at v2.9.1. MUI v7 (skipped v8, broke in v9).
|
|
42
|
-
- **`@alaarab/ogrid-js`** + **`@alaarab/ogrid-js-inputs`** — frozen at v2.9.1. The vanilla JS variant.
|
|
43
|
-
- **`@alaarab/ogrid-angular*`** — frozen at v2.9.0.
|
|
44
|
-
- **`@alaarab/ogrid-vue*`** — frozen at v2.9.0.
|
|
39
|
+
OGrid is React-first (Radix or Fluent). Material UI, vanilla JS, Angular, and Vue adapters were published earlier and remain on npm for existing installs, but are no longer actively developed.
|
|
45
40
|
|
|
46
41
|
---
|
|
47
42
|
|
|
@@ -8,7 +8,7 @@ description: What is OGrid and why use it
|
|
|
8
8
|
|
|
9
9
|
OGrid is a lightweight, open-source React data grid library that delivers spreadsheet-grade features without the enterprise paywall. It ships React UI implementations for Radix and Fluent UI, all powered by a shared TypeScript core.
|
|
10
10
|
|
|
11
|
-
> **Note:** Angular, Vue, vanilla JS, and Material UI adapters were
|
|
11
|
+
> **Note:** OGrid is React-first (Radix or Fluent). Angular, Vue, vanilla JS, and Material UI adapters were published earlier and remain on npm for existing installs, but are no longer actively developed.
|
|
12
12
|
|
|
13
13
|
## Why OGrid?
|
|
14
14
|
|
|
@@ -187,7 +187,7 @@ Users can resize columns by dragging the column border. Listen for resize events
|
|
|
187
187
|
/>
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
## Theme presets
|
|
190
|
+
## Theme presets
|
|
191
191
|
|
|
192
192
|
Instead of mapping `--ogrid-*` variables to your design system one-by-one,
|
|
193
193
|
shadcn/Tailwind v4 preset ships with `@alaarab/ogrid-react-radix`:
|
|
@@ -207,9 +207,9 @@ Both light and dark modes follow your shadcn theme automatically — no
|
|
|
207
207
|
hand-authored `[data-theme="dark"]` override blocks needed. The `.dark`
|
|
208
208
|
class on `<html>` (Tailwind v3+/shadcn convention) is fully supported.
|
|
209
209
|
|
|
210
|
-
###
|
|
210
|
+
### Theming tokens
|
|
211
211
|
|
|
212
|
-
In addition to the colors documented above,
|
|
212
|
+
In addition to the colors documented above, a radius/font/ring
|
|
213
213
|
scale that `preset-shadcn.css` bridges automatically:
|
|
214
214
|
|
|
215
215
|
| Variable | Default | Description |
|
|
@@ -63,9 +63,9 @@ function connectGridToBridge(options) {
|
|
|
63
63
|
try {
|
|
64
64
|
switch (cmd.type) {
|
|
65
65
|
case "update_cell": {
|
|
66
|
-
const rowIndex = cmd.payload
|
|
67
|
-
const columnId = cmd.payload
|
|
68
|
-
const value = cmd.payload
|
|
66
|
+
const rowIndex = cmd.payload.rowIndex;
|
|
67
|
+
const columnId = cmd.payload.columnId;
|
|
68
|
+
const value = cmd.payload.value;
|
|
69
69
|
if (onCellUpdate) {
|
|
70
70
|
onCellUpdate(rowIndex, columnId, value);
|
|
71
71
|
result = { ok: true, rowIndex, columnId, value };
|
|
@@ -75,8 +75,8 @@ function connectGridToBridge(options) {
|
|
|
75
75
|
break;
|
|
76
76
|
}
|
|
77
77
|
case "set_filter": {
|
|
78
|
-
const columnId = cmd.payload
|
|
79
|
-
const value = cmd.payload
|
|
78
|
+
const columnId = cmd.payload.columnId;
|
|
79
|
+
const value = cmd.payload.value;
|
|
80
80
|
if (api?.updateFilter) {
|
|
81
81
|
api.updateFilter(columnId, value);
|
|
82
82
|
result = { ok: true };
|
|
@@ -95,7 +95,7 @@ function connectGridToBridge(options) {
|
|
|
95
95
|
break;
|
|
96
96
|
}
|
|
97
97
|
case "set_sort": {
|
|
98
|
-
const sortModel = cmd.payload
|
|
98
|
+
const sortModel = cmd.payload.sortModel;
|
|
99
99
|
if (api?.updateSort) {
|
|
100
100
|
api.updateSort(sortModel);
|
|
101
101
|
result = { ok: true };
|
|
@@ -105,7 +105,7 @@ function connectGridToBridge(options) {
|
|
|
105
105
|
break;
|
|
106
106
|
}
|
|
107
107
|
case "go_to_page": {
|
|
108
|
-
const page = cmd.payload
|
|
108
|
+
const page = cmd.payload.page;
|
|
109
109
|
if (api?.goToPage) {
|
|
110
110
|
api.goToPage(page);
|
|
111
111
|
result = { ok: true };
|
package/dist/esm/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
|
27
27
|
function parseFrontmatter(raw) {
|
|
28
28
|
const match = FRONTMATTER_RE.exec(raw);
|
|
29
29
|
if (!match) return { title: "", description: "" };
|
|
30
|
-
const block = match[1];
|
|
30
|
+
const block = match[1] ?? "";
|
|
31
31
|
let title = "";
|
|
32
32
|
let description = "";
|
|
33
33
|
for (const line of block.split("\n")) {
|
|
@@ -56,7 +56,7 @@ function extractCodeBlocks(raw) {
|
|
|
56
56
|
CODE_BLOCK_RE.lastIndex = 0;
|
|
57
57
|
while ((match = CODE_BLOCK_RE.exec(raw)) !== null) {
|
|
58
58
|
const language = match[1] || "text";
|
|
59
|
-
const code = match[2].trim();
|
|
59
|
+
const code = (match[2] ?? "").trim();
|
|
60
60
|
const precedingStart = Math.max(0, match.index - 300);
|
|
61
61
|
const surroundingContext = raw.slice(precedingStart, match.index);
|
|
62
62
|
const framework = detectFramework(language, code, surroundingContext);
|
|
@@ -218,14 +218,15 @@ function detectOGridVersion(searchPath) {
|
|
|
218
218
|
const raw = readFileSync(pkgPath, "utf-8");
|
|
219
219
|
const pkg = JSON.parse(raw);
|
|
220
220
|
const allDeps = {
|
|
221
|
-
...pkg
|
|
222
|
-
...pkg
|
|
223
|
-
...pkg
|
|
221
|
+
...pkg.dependencies ?? {},
|
|
222
|
+
...pkg.devDependencies ?? {},
|
|
223
|
+
...pkg.peerDependencies ?? {}
|
|
224
224
|
};
|
|
225
225
|
const ogridPkgs = Object.entries(allDeps).filter(([name]) => name.startsWith("@alaarab/ogrid-")).map(([name, version]) => ({ name, version: String(version) }));
|
|
226
|
-
|
|
226
|
+
const firstPkg = ogridPkgs[0];
|
|
227
|
+
if (firstPkg) {
|
|
227
228
|
const framework = detectFramework2(ogridPkgs.map((p) => p.name));
|
|
228
|
-
const version =
|
|
229
|
+
const version = firstPkg.version.replace(/^[\^~>=<]+/, "");
|
|
229
230
|
return { found: true, version, framework, packages: ogridPkgs, packageJsonPath: pkgPath };
|
|
230
231
|
}
|
|
231
232
|
} catch {
|
|
@@ -280,9 +281,10 @@ Categories: features, getting-started, guides, api.`
|
|
|
280
281
|
const formatted = results.map((entry, i) => {
|
|
281
282
|
const excerpt = entry.content.length > 400 ? entry.content.slice(0, 400) + "..." : entry.content;
|
|
282
283
|
const relevantCode = framework ? entry.codeBlocks.filter((b) => !b.framework || b.framework === framework).slice(0, 1) : [];
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
const firstCode = relevantCode[0];
|
|
285
|
+
const codeSnippet = firstCode ? `
|
|
286
|
+
\`\`\`${firstCode.language}
|
|
287
|
+
${firstCode.code.slice(0, 300)}
|
|
286
288
|
\`\`\`` : "";
|
|
287
289
|
return [
|
|
288
290
|
`## ${i + 1}. ${entry.title}`,
|
|
@@ -537,6 +539,7 @@ Tip: use \`get_code_example\` with framework="${result.framework}" or \`search_d
|
|
|
537
539
|
"```tsx",
|
|
538
540
|
"const dataSource = {",
|
|
539
541
|
" fetchPage: async ({ page, pageSize, sort, filters, signal }) => {",
|
|
542
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional — this is documentation example code emitted as text; the ${} placeholders belong to the sample snippet
|
|
540
543
|
" const res = await fetch(`/api/data?page=${page}&size=${pageSize}`, { signal });",
|
|
541
544
|
" const json = await res.json();",
|
|
542
545
|
" return { items: json.data, totalCount: json.total };",
|
|
@@ -964,26 +967,37 @@ function readBody(req) {
|
|
|
964
967
|
req.on("error", reject);
|
|
965
968
|
});
|
|
966
969
|
}
|
|
967
|
-
|
|
970
|
+
var LOCALHOST_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
|
|
971
|
+
function corsOrigin(req) {
|
|
972
|
+
const origin = req.headers.origin;
|
|
973
|
+
return origin && LOCALHOST_ORIGIN.test(origin) ? origin : "";
|
|
974
|
+
}
|
|
975
|
+
function send(req, res, status, body) {
|
|
968
976
|
const json = JSON.stringify(body);
|
|
969
|
-
|
|
977
|
+
const headers = {
|
|
970
978
|
"Content-Type": "application/json",
|
|
971
979
|
"Content-Length": Buffer.byteLength(json),
|
|
972
|
-
|
|
980
|
+
Vary: "Origin",
|
|
973
981
|
"Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS",
|
|
974
982
|
"Access-Control-Allow-Headers": "Content-Type"
|
|
975
|
-
}
|
|
983
|
+
};
|
|
984
|
+
const allow = corsOrigin(req);
|
|
985
|
+
if (allow) headers["Access-Control-Allow-Origin"] = allow;
|
|
986
|
+
res.writeHead(status, headers);
|
|
976
987
|
res.end(json);
|
|
977
988
|
}
|
|
978
989
|
function startBridgeServer(store, port = 7890) {
|
|
979
990
|
return new Promise((resolve, reject) => {
|
|
980
991
|
const httpServer = createServer(async (req, res) => {
|
|
981
992
|
if (req.method === "OPTIONS") {
|
|
982
|
-
|
|
983
|
-
"Access-Control-Allow-Origin": "*",
|
|
993
|
+
const headers = {
|
|
984
994
|
"Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS",
|
|
985
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
986
|
-
|
|
995
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
996
|
+
Vary: "Origin"
|
|
997
|
+
};
|
|
998
|
+
const allow = corsOrigin(req);
|
|
999
|
+
if (allow) headers["Access-Control-Allow-Origin"] = allow;
|
|
1000
|
+
res.writeHead(204, headers);
|
|
987
1001
|
res.end();
|
|
988
1002
|
return;
|
|
989
1003
|
}
|
|
@@ -991,45 +1005,45 @@ function startBridgeServer(store, port = 7890) {
|
|
|
991
1005
|
const parts = url.pathname.replace(/^\//, "").split("/");
|
|
992
1006
|
try {
|
|
993
1007
|
if (req.method === "GET" && parts[0] === "health") {
|
|
994
|
-
send(res, 200, { ok: true, grids: store.listGrids().length });
|
|
1008
|
+
send(req, res, 200, { ok: true, grids: store.listGrids().length });
|
|
995
1009
|
return;
|
|
996
1010
|
}
|
|
997
1011
|
if (req.method === "POST" && parts[0] === "grids" && parts[1] === "connect") {
|
|
998
1012
|
const body = await readBody(req);
|
|
999
|
-
const gridId = String(body?.
|
|
1013
|
+
const gridId = String(body?.gridId ?? "");
|
|
1000
1014
|
if (!gridId) {
|
|
1001
|
-
send(res, 400, { error: "gridId required" });
|
|
1015
|
+
send(req, res, 400, { error: "gridId required" });
|
|
1002
1016
|
return;
|
|
1003
1017
|
}
|
|
1004
1018
|
store.upsertGrid(gridId, body);
|
|
1005
|
-
send(res, 200, { ok: true });
|
|
1019
|
+
send(req, res, 200, { ok: true });
|
|
1006
1020
|
return;
|
|
1007
1021
|
}
|
|
1008
|
-
if (req.method === "PUT" && parts[0] === "grids" && parts[2] === "state") {
|
|
1022
|
+
if (req.method === "PUT" && parts[0] === "grids" && parts[1] != null && parts[2] === "state") {
|
|
1009
1023
|
const gridId = parts[1];
|
|
1010
1024
|
const body = await readBody(req);
|
|
1011
1025
|
store.upsertGrid(gridId, body);
|
|
1012
|
-
send(res, 200, { ok: true });
|
|
1026
|
+
send(req, res, 200, { ok: true });
|
|
1013
1027
|
return;
|
|
1014
1028
|
}
|
|
1015
|
-
if (req.method === "GET" && parts[0] === "grids" && parts[2] === "commands") {
|
|
1029
|
+
if (req.method === "GET" && parts[0] === "grids" && parts[1] != null && parts[2] === "commands") {
|
|
1016
1030
|
const gridId = parts[1];
|
|
1017
1031
|
const state = store.getState(gridId);
|
|
1018
1032
|
if (state) store.upsertGrid(gridId, {});
|
|
1019
1033
|
const cmds = store.popPendingCommands(gridId);
|
|
1020
|
-
send(res, 200, cmds);
|
|
1034
|
+
send(req, res, 200, cmds);
|
|
1021
1035
|
return;
|
|
1022
1036
|
}
|
|
1023
|
-
if (req.method === "POST" && parts[0] === "grids" && parts[2] === "commands" && parts[4] === "result") {
|
|
1037
|
+
if (req.method === "POST" && parts[0] === "grids" && parts[2] === "commands" && parts[3] != null && parts[4] === "result") {
|
|
1024
1038
|
const cmdId = parts[3];
|
|
1025
1039
|
const body = await readBody(req);
|
|
1026
|
-
store.resolveCommand(cmdId, body?.
|
|
1027
|
-
send(res, 200, { ok: true });
|
|
1040
|
+
store.resolveCommand(cmdId, body?.result, body?.error);
|
|
1041
|
+
send(req, res, 200, { ok: true });
|
|
1028
1042
|
return;
|
|
1029
1043
|
}
|
|
1030
|
-
send(res, 404, { error: "Not found" });
|
|
1044
|
+
send(req, res, 404, { error: "Not found" });
|
|
1031
1045
|
} catch (err) {
|
|
1032
|
-
send(res, 500, { error: String(err) });
|
|
1046
|
+
send(req, res, 500, { error: String(err) });
|
|
1033
1047
|
}
|
|
1034
1048
|
});
|
|
1035
1049
|
httpServer.on("error", reject);
|
|
@@ -1053,10 +1067,10 @@ if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
|
1053
1067
|
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
1054
1068
|
var monorepoDocs = join(__dirname$1, "../../../docs/docs");
|
|
1055
1069
|
var bundledDocs = join(__dirname$1, "../../bundled-docs");
|
|
1056
|
-
var docsDir = process.env
|
|
1070
|
+
var docsDir = process.env.OGRID_DOCS_PATH ?? (existsSync(monorepoDocs) ? monorepoDocs : bundledDocs);
|
|
1057
1071
|
var index = loadDocsIndex(docsDir);
|
|
1058
1072
|
var bridgeStore = new BridgeStore();
|
|
1059
|
-
var bridgePort = process.env
|
|
1073
|
+
var bridgePort = process.env.OGRID_BRIDGE_PORT ? parseInt(process.env.OGRID_BRIDGE_PORT, 10) : process.argv.includes("--bridge") ? 7890 : null;
|
|
1060
1074
|
if (bridgePort !== null) {
|
|
1061
1075
|
try {
|
|
1062
1076
|
await startBridgeServer(bridgeStore, bridgePort);
|