Skip to content

Commit a08b0e9

Browse files
author
Asif Ansari
committed
chore: release v2.0.0
Breaking changes: - Remove onPinnedRowsChange and onRowGroupingModelChange from DataGridProps (both were dead — callbacks never called, no triggering UI existed) New features: - multiSort prop: single-click appends to sortModel instead of replacing - Multi-sort shift-click: shift+click now appends; previously behaved like plain click - density prop wired: compact=32px, standard=rowHeight, comfortable=72px via --ogx-row-height - disableRowSelectionOnClick and disableMultipleRowSelection now wired in handleRowClick; onRowSelectionModelChange fires correctly on click - groupingColDef now creates a real __group__ column at position 0, auto-pinned left - groupable: false honored in useRowGrouping — skips field when building group tree - groupingValueFormatter on GridColDef — custom group-header label via GridRowMeta.groupLabel - availableAggregationFunctions now enforced in useAggregation - description on GridColDef renders as title attribute on header cell Bug fixes: - Pagination broken for uncontrolled usage: paginationModel default value caused isPaginationControlled to always be true, preventing page-change handler from updating internal state. Fixed by removing default from prop destructure. Docs and demo: - docs/migration/v1-to-v2.md: new migration guide - demo/pages/MigrationV2.tsx: migration guide visible in demo app - demo/examples/MultiSortDemo: new demo showcasing multiSort prop - All component docs, feature docs, API_REFERENCE, CHANGELOG, llms.txt updated - Version bumped in package.json, Home.tsx, App.tsx sidebar, llms.txt
1 parent 0418886 commit a08b0e9

35 files changed

Lines changed: 1387 additions & 80 deletions

.agent/project-context.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- **Project Path**: `/Volumes/Data/RND/OpenGridX`
77
- **Core Value**: 100% custom implementation with ZERO external UI dependencies.
88
- **Performance**: Handles 100,000+ rows via custom virtualization at 60fps.
9-
- **Current Version**: `0.1.5` (published to npm)
9+
- **Current Version**: `2.0.0` (unpublished — pending release)
1010

1111
## 📦 NPM Publication Status
1212
- **v0.1.0** → Published ✅ (March 6, 2026 — first publish)
@@ -15,6 +15,12 @@
1515
- **v0.1.3** → Published ✅ (March 10, 2026 — CSS auto-import fix via barrel entry)
1616
- **v0.1.4** → Published ✅ (March 10, 2026 — Column Visibility DnD reorder + CSS subpath export fixes)
1717
- **v0.1.5** → Published ✅ (March 10, 2026 — Exported missing public types: GridSortItem, GridApi, etc.)
18+
- **v1.1.0** → Published ✅ (GridRowMeta pattern, CellErrorBoundary, GridLocaleText, duplicate ID fix)
19+
- **v1.2.1** → Published ✅ (PDF export, CellErrorBoundary fix, SPA routing)
20+
- **v1.2.2** → Published ✅ (CellErrorBoundary CellRenderTarget, getAllFilteredRows, PDF aggregation footer)
21+
- **v1.2.3** → Published ✅ (hideable:false excluded from Columns panel by default)
22+
- **v1.3.0** → Published ✅ (GridApi.getGroupedExportRows, grouped export across all formats)
23+
- **v2.0.0** → Unpublished — pending release (density, multiSort, disableRowSelectionOnClick, disableMultipleRowSelection, groupingColDef, groupable:false, groupingValueFormatter, availableAggregationFunctions, pagination bug fix, removed onPinnedRowsChange + onRowGroupingModelChange)
1824
- **Package URL**: https://www.npmjs.com/package/@opencorestack/opengridx
1925
- **npm org**: `opencorestack` (owner: `asif7774`)
2026
- **Next publish**: bump version in `package.json`, then `npm publish --access public --otp=<code>`

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,47 @@
55

66
---
77

8+
## [2.0.0] — 2026-09-08
9+
10+
### Breaking
11+
12+
- **Removed dead public API:** `onPinnedRowsChange` and `onRowGroupingModelChange` are removed from
13+
`DataGridProps`. Both props accepted callbacks that were never invoked — no pin/unpin-row UI and no
14+
drag-to-group UI exist. Callers that passed these callbacks should simply remove them; grid
15+
behavior is unchanged.
16+
17+
### Added
18+
19+
- **`density` wired**`density?: 'compact' | 'standard' | 'comfortable'` now sets the
20+
`--ogx-row-height` CSS variable (compact = 32 px, standard = `rowHeight`, comfortable = 72 px).
21+
Previously accepted but silently discarded.
22+
- **`disableRowSelectionOnClick`** — clicking a row no longer triggers selection when this prop is
23+
`true`. Previously wired to internal state only; `onRowSelectionModelChange` is now also fired.
24+
- **`disableMultipleRowSelection`** — clicking a row while this prop is `true` caps selection to
25+
that single row (deselects others). Clicking an already-selected row deselects it.
26+
- **`groupingColDef` implemented** — passing `groupingColDef` when `rowGroupingModel` is active now
27+
creates a dedicated `__group__` column at position 0, auto-pinned left. Previously the prop was
28+
accepted but had no runtime effect.
29+
- **`groupable: false` honored** — columns with `groupable: false` are now skipped when building the
30+
grouping tree in `useRowGrouping`. Previously this flag was silently ignored.
31+
- **`groupingValueFormatter` on `GridColDef`** — new optional field
32+
`groupingValueFormatter?: (params: { field: string; value: unknown }) => string` customizes the
33+
label shown for group-header rows at that level. The formatted string flows through
34+
`GridRowMeta.groupLabel`. Falls back to `"${field}: ${value}"` when omitted.
35+
- **`availableAggregationFunctions` honored** — per-column `availableAggregationFunctions?: string[]`
36+
now gates which aggregation functions are computed in `useAggregation`. Functions not in the
37+
allowed list are skipped for that field.
38+
- **`multiSort` prop** — new `multiSort?: boolean` on `DataGrid`. When `true`, every click on a
39+
sortable column header appends/cycles that column in the sort model instead of replacing it — no
40+
Shift key required. Shift+click continues to work as an append gesture regardless of this prop.
41+
- **Multi-sort shift-click** — shift-clicking a column header appends the column to `sortModel`
42+
rather than replacing it. A numbered priority badge appears next to the sort arrow when more than
43+
one sort key is active. Plain click still replaces with a single-key sort (unless `multiSort` is set).
44+
- **`description` tooltip**`description?: string` on `GridColDef` now renders as the native
45+
`title` attribute on the header cell, providing a browser tooltip on hover.
46+
47+
---
48+
849
## [1.3.0] — 2026-09-08
950

1051
### Added

CLAUDE.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This file is auto-loaded by Claude Code and other AI coding assistants. It provi
1313
- **Tests:** Vitest 4 + `@testing-library/react` (`renderHook` for hooks, component tests for UI)
1414
- **Build:** Vite (library mode). `npm run build` produces `dist/opengridx.es.js` and `dist/opengridx.umd.js`.
1515
- **Lint:** `npm run lint` (ESLint). Must pass before every commit.
16-
- **Current version:** 1.3.0
16+
- **Current version:** 2.0.0
1717

1818
---
1919

@@ -127,6 +127,52 @@ Full doc: `docs/architecture/grid-row-meta.md`
127127

128128
---
129129

130+
## v2.0.0 — significant changes (breaking)
131+
132+
### Breaking: removed dead public API surface
133+
134+
The following props/types existed in v1.x but had no runtime implementation. They have been **removed** from the public TypeScript types:
135+
136+
| Removed | Type | Reason |
137+
| :--- | :--- | :--- |
138+
| `onPinnedRowsChange` | `DataGridProps` | No pin/unpin-row UI exists; callback was never called |
139+
| `onRowGroupingModelChange` | `DataGridProps` | No drag-to-group UI; callback was never called |
140+
| `description` | `GridColDef` | Not read anywhere in the grid |
141+
142+
### New: `density` prop wired
143+
144+
`density?: 'compact' | 'standard' | 'comfortable'` now controls `effectiveRowHeight` (compact=32, standard=`rowHeight`, comfortable=72) via CSS `--ogx-row-height`. Previously the prop was accepted but silently discarded.
145+
146+
### New: `disableRowSelectionOnClick` and `disableMultipleRowSelection`
147+
148+
Both props are now wired in `handleRowClick`. `disableRowSelectionOnClick` suppresses click-to-select; `disableMultipleRowSelection` caps selection to a single row.
149+
150+
### New: `groupingColDef` implemented
151+
152+
When `rowGroupingModel` is active, passing `groupingColDef` now creates a dedicated synthetic `__group__` column at position 0, auto-pinned left. Previously the prop was accepted but had no runtime effect.
153+
154+
### New: `groupable: false` honored
155+
156+
Columns with `groupable: false` are now skipped when building the grouping tree in `useRowGrouping`. Previously this flag was silently ignored.
157+
158+
### New: `groupingValueFormatter` on `GridColDef`
159+
160+
```ts
161+
groupingValueFormatter?: (params: { field: string; value: unknown }) => string
162+
```
163+
164+
Customizes the label for group-header rows at that level. Falls back to `"${field}: ${value}"` when omitted. The formatted string flows through `GridRowMeta.groupLabel` so `useGridColumns` picks it up for rendering.
165+
166+
### New: `availableAggregationFunctions` honored
167+
168+
Per-column `availableAggregationFunctions?: string[]` now gates which aggregation functions are computed in `useAggregation`. If the current `aggregationModel` entry for a field uses a function not in that list, the aggregation is skipped for that field.
169+
170+
### New: Multi-sort shift-click
171+
172+
Shift-clicking a column header now appends to (or removes from) `sortModel` rather than replacing it. The sort priority badge (1, 2, 3…) appears next to the sort arrow when more than one sort key is active. Plain click still replaces with a single-key sort.
173+
174+
---
175+
130176
## v1.3.0 — significant changes
131177

132178
- **`GridApi.getGroupedExportRows()`** — new method returning a flat `GridGroupedExportRow[]` ordered list by traversing the active row-grouping tree (group-header → children → subtotal → grand-total). Returns `null` when row grouping is not active.

demo/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const ScrollToIndexesDemo = lazy(() => import('./examples/ScrollToIndexesDemo/Sc
4242
const LocaleTextDemo = lazy(() => import('./examples/LocaleTextDemo/LocaleTextDemo'));
4343
const CellErrorBoundaryDemo = lazy(() => import('./examples/CellErrorBoundaryDemo/CellErrorBoundaryDemo'));
4444
const PdfExportDemo = lazy(() => import('./examples/PdfExportDemo/PdfExportDemo'));
45+
const MultiSortDemo = lazy(() => import('./examples/MultiSortDemo/MultiSortDemo'));
4546

4647
const examplesConfig = [
4748
// Resources
@@ -51,6 +52,7 @@ const examplesConfig = [
5152
{ path: '/api-reference', name: 'API Reference', component: lazy(() => import('./pages/APIDocumentation')), category: 'Resources' },
5253
{ path: '/licensing', name: 'Licensing', component: lazy(() => import('./pages/Licensing')), category: 'Resources' },
5354
{ path: '/support', name: 'Support', component: lazy(() => import('./pages/Support')), category: 'Resources' },
55+
{ path: '/migration-v2', name: 'Migrating to v2', component: lazy(() => import('./pages/MigrationV2')), category: 'Resources' },
5456

5557
// Main Features
5658
{ path: '/basic', name: 'Basic Usage', component: BasicExample, category: 'Main features' },
@@ -59,6 +61,7 @@ const examplesConfig = [
5961
{ path: '/pdf-export', name: 'PDF Export', component: PdfExportDemo, category: 'Main features' },
6062
{ path: '/export', name: 'Export Data', component: ExportDemo, category: 'Main features' },
6163
{ path: '/clipboard', name: 'Clipboard Copy', component: ClipboardDemo, category: 'Main features' },
64+
{ path: '/multi-sort', name: 'Multi-Column Sort', component: MultiSortDemo, category: 'Main features' },
6265
{ path: '/filtering', name: 'Advanced Filtering', component: AdvancedFilteringDemo, category: 'Main features' },
6366
{ path: '/editing', name: 'Cell Editing', component: EditingExample, category: 'Main features' },
6467
{ path: '/events', name: 'Events Observer', component: EventsDemo, category: 'Main features' },
@@ -143,7 +146,7 @@ export default function App() {
143146
<img src={`${import.meta.env.BASE_URL}logo.png`} alt="OpenGridX Logo" className="app-logo" />
144147
<h2 className="app-title">
145148
OpenGridX
146-
<span className="app-version">v1.2.1</span>
149+
<span className="app-version">v2.0.0</span>
147150
</h2>
148151
</div>
149152

demo/Home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default function Home(_props: HomeProps) {
6262
<div className="home-logo-hero">
6363
<img src={`${import.meta.env.BASE_URL}banner.png`} alt="OpenGridX Logo" className="home-banner-image" />
6464
</div>
65-
<span className="home-badge">OpenGridX v1.2.1</span>
65+
<span className="home-badge">OpenGridX v2.0.0</span>
6666
<p className="home-subtitle">
6767
The elite, high-performance DataGrid for modern React.
6868
Built to handle massive data with a premium developer experience.

demo/examples/Grouping/Grouping.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,57 @@
11

22
import { DataGrid, exportToCsv, exportToJson, exportToExcel, useGridApiRef } from '@opencorestack/opengridx';
3+
import type { GridColDef } from '@opencorestack/opengridx';
34
import { columnDefinitions, mockRows } from '../../mockData';
45
import './Grouping.css';
56
import { DocsLayout } from '../../components/DocsLayout';
67
import sourceCode from './Grouping.tsx?raw';
78
import { Button } from '../../../lib/components/ui/Button';
89

10+
// Override selected columns to demonstrate v2.0 grouping features:
11+
// - groupingValueFormatter: custom label for group-header rows
12+
// - groupable: false: prevents a column from being used as a grouping key
13+
const groupingColumns: GridColDef[] = columnDefinitions.map((col) => {
14+
if (col.field === 'department') {
15+
return {
16+
...col,
17+
groupingValueFormatter: ({ value }) => `📁 ${String(value)}`,
18+
};
19+
}
20+
if (col.field === 'country') {
21+
return {
22+
...col,
23+
groupingValueFormatter: ({ value }) => `🌍 ${String(value)}`,
24+
};
25+
}
26+
// Prevent grouping by the id column
27+
if (col.field === 'id') {
28+
return { ...col, groupable: false };
29+
}
30+
return col;
31+
});
32+
933
export default function GroupingExample() {
1034
const apiRef = useGridApiRef();
1135

1236
const handleGroupedCsv = () => {
1337
const groupedRows = apiRef.current.getGroupedExportRows();
14-
exportToCsv(mockRows, columnDefinitions, {
38+
exportToCsv(mockRows, groupingColumns, {
1539
fileName: 'grouped-export.csv',
1640
groupedRows: groupedRows ?? undefined,
1741
});
1842
};
1943

2044
const handleGroupedExcel = () => {
2145
const groupedRows = apiRef.current.getGroupedExportRows();
22-
exportToExcel(mockRows, columnDefinitions, {
46+
exportToExcel(mockRows, groupingColumns, {
2347
fileName: 'grouped-export.xls',
2448
groupedRows: groupedRows ?? undefined,
2549
});
2650
};
2751

2852
const handleGroupedJson = () => {
2953
const groupedRows = apiRef.current.getGroupedExportRows();
30-
exportToJson(mockRows, columnDefinitions, {
54+
exportToJson(mockRows, groupingColumns, {
3155
fileName: 'grouped-export.json',
3256
groupedRows: groupedRows ?? undefined,
3357
});
@@ -36,7 +60,7 @@ export default function GroupingExample() {
3660
return (
3761
<DocsLayout
3862
title="Row Grouping"
39-
description="Group rows by any column value. OpenGridX aggregates child rows automatically and shows group counts in collapsible parent rows. The grouped structure can be preserved in exports via getGroupedExportRows()."
63+
description="Group rows by any column value. groupingValueFormatter customizes group-header labels; groupable:false prevents a field from being used as a grouping key; groupingColDef pins the expand/collapse indicator to its own dedicated column."
4064
sourceCode={sourceCode}
4165
>
4266
<div style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' }}>
@@ -47,8 +71,9 @@ export default function GroupingExample() {
4771
<DataGrid
4872
apiRef={apiRef}
4973
rows={mockRows}
50-
columns={columnDefinitions}
74+
columns={groupingColumns}
5175
rowGroupingModel={['department', 'country']}
76+
groupingColDef={{ headerName: 'Group', width: 220 }}
5277
pagination={true}
5378
pageSizeOptions={[10, 25, 50]}
5479
/>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.multi-sort-hint {
2+
background: var(--ogx-color-primary, #3b82f6);
3+
color: #fff;
4+
border-radius: 6px;
5+
padding: 8px 14px;
6+
font-size: 13px;
7+
margin-bottom: 10px;
8+
}
9+
10+
.multi-sort-state {
11+
display: flex;
12+
align-items: center;
13+
gap: 8px;
14+
margin-bottom: 12px;
15+
font-size: 13px;
16+
color: var(--ogx-color-text-secondary, #6b7280);
17+
}
18+
19+
.multi-sort-label {
20+
font-weight: 600;
21+
white-space: nowrap;
22+
}
23+
24+
.multi-sort-state code {
25+
background: var(--ogx-color-surface-secondary, #f3f4f6);
26+
border-radius: 4px;
27+
padding: 2px 8px;
28+
font-size: 12px;
29+
word-break: break-all;
30+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
import { useState } from 'react';
3+
import { DataGrid } from '@opencorestack/opengridx';
4+
import type { GridColDef, GridSortModel } from '@opencorestack/opengridx';
5+
import './MultiSortDemo.css';
6+
import { DocsLayout } from '../../components/DocsLayout';
7+
import sourceCode from './MultiSortDemo.tsx?raw';
8+
9+
// Focused dataset: 4 departments, distinct salaries — makes multi-sort result
10+
// immediately readable: dept ASC + salary DESC → highest-paid per dept appears first.
11+
const ROWS = [
12+
{ id: 1, name: 'Alice Chen', department: 'Engineering', salary: 120000, level: 'Senior' },
13+
{ id: 2, name: 'Bob Martin', department: 'Engineering', salary: 95000, level: 'Mid' },
14+
{ id: 3, name: 'Carol White', department: 'Engineering', salary: 145000, level: 'Staff' },
15+
{ id: 4, name: 'David Kim', department: 'Engineering', salary: 80000, level: 'Junior' },
16+
{ id: 5, name: 'Eva Patel', department: 'HR', salary: 72000, level: 'Senior' },
17+
{ id: 6, name: 'Frank Lee', department: 'HR', salary: 58000, level: 'Mid' },
18+
{ id: 7, name: 'Grace Nguyen', department: 'HR', salary: 65000, level: 'Senior' },
19+
{ id: 8, name: 'Henry Walsh', department: 'Marketing', salary: 88000, level: 'Staff' },
20+
{ id: 9, name: 'Iris Santos', department: 'Marketing', salary: 75000, level: 'Senior' },
21+
{ id: 10, name: 'Jack Okafor', department: 'Marketing', salary: 62000, level: 'Mid' },
22+
{ id: 11, name: 'Karen Zhou', department: 'Sales', salary: 105000, level: 'Staff' },
23+
{ id: 12, name: 'Liam Ross', department: 'Sales', salary: 91000, level: 'Senior' },
24+
{ id: 13, name: 'Mia Torres', department: 'Sales', salary: 77000, level: 'Mid' },
25+
{ id: 14, name: 'Noah Bakker', department: 'Sales', salary: 68000, level: 'Junior' },
26+
{ id: 15, name: 'Olivia Grant', department: 'Engineering', salary: 110000, level: 'Senior' },
27+
{ id: 16, name: 'Paul Dubois', department: 'Marketing', salary: 93000, level: 'Staff' },
28+
{ id: 17, name: 'Quinn Tran', department: 'HR', salary: 55000, level: 'Junior' },
29+
{ id: 18, name: 'Rosa Ferreira', department: 'Sales', salary: 115000, level: 'Staff' },
30+
{ id: 19, name: 'Sam Johansson', department: 'Engineering', salary: 70000, level: 'Junior' },
31+
{ id: 20, name: 'Tina Yamamoto', department: 'Marketing', salary: 85000, level: 'Senior' },
32+
];
33+
34+
const columns: GridColDef[] = [
35+
{ field: 'name', headerName: 'Name', width: 170 },
36+
{ field: 'department', headerName: 'Department', width: 140 },
37+
{ field: 'level', headerName: 'Level', width: 110 },
38+
{ field: 'salary', headerName: 'Salary', width: 110, type: 'number' },
39+
{ field: 'id', headerName: 'ID', width: 60, sortable: false,
40+
description: 'Row ID — sortable: false, click should do nothing' },
41+
];
42+
43+
export default function MultiSortDemo() {
44+
const [sortModel, setSortModel] = useState<GridSortModel>([
45+
{ field: 'department', sort: 'asc' },
46+
{ field: 'salary', sort: 'desc' },
47+
]);
48+
49+
return (
50+
<DocsLayout
51+
title="Multi-Column Sorting"
52+
description="Pass multiSort to enable single-click multi-column sorting — every click appends or cycles a column instead of replacing the sort. Numbered badges show sort priority. Clicking a column again cycles asc → desc → removed without touching other keys."
53+
sourceCode={sourceCode}
54+
>
55+
<div className="multi-sort-hint">
56+
<strong>How to verify:</strong> The grid starts sorted by Department ① asc + Salary ② desc.
57+
Click <em>Level</em> to add a third sort key — note the existing two stay active.
58+
Click <em>Department</em> again to cycle it desc, then again to remove it.
59+
Clicking <em>ID</em> should do nothing (sortable: false).
60+
</div>
61+
<div className="multi-sort-state">
62+
<span className="multi-sort-label">sortModel:</span>
63+
<code>{JSON.stringify(sortModel)}</code>
64+
</div>
65+
<DataGrid
66+
rows={ROWS}
67+
columns={columns}
68+
multiSort
69+
sortModel={sortModel}
70+
onSortModelChange={setSortModel}
71+
pagination
72+
pageSizeOptions={[10, 20]}
73+
initialState={{ pagination: { paginationModel: { pageSize: 20, page: 0 } } }}
74+
height={520}
75+
/>
76+
</DocsLayout>
77+
);
78+
}

demo/pages/Docs.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,26 @@
201201
gap: 16px;
202202
justify-content: center;
203203
}
204+
205+
.docs-checklist {
206+
display: flex;
207+
flex-direction: column;
208+
gap: 10px;
209+
}
210+
211+
.docs-checklist label {
212+
display: flex;
213+
align-items: flex-start;
214+
gap: 10px;
215+
font-size: 14px;
216+
cursor: default;
217+
line-height: 1.5;
218+
}
219+
220+
.docs-checklist input[type="checkbox"] {
221+
margin-top: 2px;
222+
flex-shrink: 0;
223+
width: 16px;
224+
height: 16px;
225+
cursor: default;
226+
}

0 commit comments

Comments
 (0)