Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ body:
id: version
attributes:
label: OpenGridX version
placeholder: "3.7.0"
placeholder: "e.g. 3.9.0 (npm ls @opencorestack/opengridx)"
validations:
required: true
- type: dropdown
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ jobs:
- name: Build library (fails if React is bundled)
run: npm run build:lib

- name: Package contents and size budget (no test artifacts; DataGrid alone within budget)
run: npm run check:package

- name: Package smoke suite (packed tarball in React 18 and 19 apps)
run: npm run test:smoke -- --skip-lib

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
- name: Build library (fails if React is bundled)
run: npm run build:lib

- name: Package contents and size budget
run: npm run check:package

- name: Package smoke suite (packed tarball in React 18 and 19 apps)
run: npm run test:smoke -- --skip-lib

Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@

---

## [3.9.1] — 2026-10-13

### Fixed

- **The npm package no longer ships the theme test baselines.** `lib/theme/__baseline__/` (three JSON files, 1.6 MB, added in 3.7.0) was published because `files` excluded tests, screenshots and snapshots but not that folder. Unpacked size 9.4 → 7.8 MB.
- The demo's home page badge showed v3.7.0; it and the header now read the version from `package.json`.

### Internal

- **`npm run check:package`**, run in CI and before publishing: fails when `npm pack` would ship a test artifact or when `DataGrid` alone, minified and gzipped, grows past its budget (92 KB; 85.5 KB now).

### Documentation

- Roadmap brought up to date (3.5.1 – 3.9.1 shipped; new features on hold until the backlog of fixes, refactors and performance work is closed; the 4.0 feature-modules proposal). `docs/performance.md`: edit, paste, fill, group toggle and select-all timings at 1M rows and how to keep rows memoised. README: spreadsheet editing, Ask AI panel, `localeText`, RTL and Theme Builder in the feature list; bundle sizes re-measured. `rtl.md` and `components/pagination.md` no longer say their labels cannot be translated. Architecture notes on the column-window memo and the locale context. The wiki home links the newer feature pages.

---

## [3.9.0] — 2026-10-13

### Added
Expand Down
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This file is auto-loaded by Claude Code and other AI coding assistants. It provi
- **Build:** `npm run build:lib` produces the published `dist/opengridx.es.js` / `dist/opengridx.umd.js`. `npm run build` type-checks the lib and builds the **demo site**.
- **Published:** yes — on npm as `@opencorestack/opengridx` with external consumers. Treat public types and behaviour changes as semver-relevant.
- **Lint:** `npm run lint` (ESLint). Must pass before every commit.
- **Current version:** 3.9.0
- **Current version:** 3.9.1

---

Expand Down Expand Up @@ -157,6 +157,10 @@ The GitHub wiki is generated from `docs/` + `wiki/` by `scripts/build-wiki.mjs`

---

## v3.9.1 — significant changes

- **`npm run check:package`** (after `build:lib`; in `ci.yml` and `npm-publish.yml`): fails if `npm pack` would ship a test artifact (`*.test.*`, `__baseline__`, `__screenshots__`, `__snapshots__`, `__fixtures__`) and if `DataGrid` alone, minified with esbuild and gzipped, exceeds `DATAGRID_GZIP_BUDGET` (92 KB; 85.5 KB at 3.9.0). `files` in package.json works by exclusion: add a negation for any new kind of test folder under `lib/`. Raise the budget only in the commit that needs it

## v3.9.0 — significant changes

- **Rows stay memoised.** Every prop passed to `Row` must keep its identity unless that row changed: handlers go through the latest-ref pattern (`useGridRowInteractions`: stable `handleRowClick` / `handleSelectionChange`), per-row state is passed only to the row it concerns (`editingCell`, `focusedCellField`, `cellRange`), and `useGridVirtualization` builds `virtualColumns` in its own memo over column inputs only. `DataGrid.rowMemo.test.tsx` counts `Row` renders through `isCellEditable` (`Cell` is memoised, so `renderCell` would not show them)
Expand Down Expand Up @@ -189,7 +193,7 @@ The GitHub wiki is generated from `docs/` + `wiki/` by `scripts/build-wiki.mjs`
- **RTL**: read/write horizontal scroll only through `getScrollStart` / `setScrollStart` (`lib/utils/direction.ts`; scroll state is the offset from the inline start, never negative); map arrows with `logicalArrowKey(key, isRtl(el))`; panels with `getPanelPosition`. `pinnedColumns.left` / `right` are logical (inline start / end): sticky offsets use `insetInlineStart` / `insetInlineEnd` (jsdom: `style.getPropertyValue('inset-inline-start')`), borders `border-inline-*`, box-shadow x-offsets need `:dir(rtl)` mirrors. The column menu pins by screen side. `useGridScrollSync` observes `dir` mutations
- Testing RTL: Playwright's headless Chromium runs with `--hide-scrollbars`; with classic scrollbars and `scrollbar-gutter: stable`, RTL cannot reach `scrollLeft` 0 — compare against the scroll origin. Reproduce on macOS with `defaults write chrome-headless-shell AppleShowScrollBars Always`. The grid's `scrollbar-width` / `scrollbar-color` disable `::-webkit-scrollbar` in Chromium
- Header filter strings are in `localeText` (`headerFilter*`, resolved in `lib/utils/headerFilterLocale.ts`)
- Not in this release: the time-sliced pipeline (Option A of `docs/superpowers/specs/2026-10-12-nonblocking-pipeline-proposal.md`) is held for 3.7.0 until its overhead is within ~20 %
- Not in this release: the time-sliced pipeline (Option A of `docs/superpowers/specs/2026-10-12-nonblocking-pipeline-proposal.md`) is held (backlog) until its overhead is within ~20 %

## v3.5.1 — significant changes

Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ Without a `height` prop the grid fills its container (`height: 100%`, or the fre
- **Fully Customizable**: Slots system for replacing any component (pagination, overlays, toolbar).
- **Export Functionality**: Built-in CSV, Excel (HTML `.xls`, or real `.xlsx` with the optional ExcelJS peer), JSON, PDF (optional jsPDF peer) and Print export.
- **Clipboard**: `Ctrl+C` / `Cmd+C` copies the selected rows' visible columns as TSV for Excel/Sheets.
- **Spreadsheet editing** (v3.4–3.5): paste from Excel / Google Sheets into a cell range (parsed per column type, through `processRowUpdate`), Delete clears a range, **undo / redo** (`undoRedo`, Ctrl+Z / Ctrl+Shift+Z), and a **fill handle** that extends series, patterns or copies (Ctrl/Cmd+D / R).
- **Header Filters** (v3.5): `headerFilters` adds a filter row under the column headers (text, number, date and select controls with an operator menu) that edits the same `filterModel` as the filter panel and the quick filter.
- **Cell Range Selection** (v3.3): drag, Shift+click or Shift+arrows select a rectangle of cells; copy it as TSV and see its count, sum and average in a status bar (`cellSelection`, `showCellSelectionStats`).
- **AI Toolkit** (v3.4): `@opencorestack/opengridx/ai` turns your columns into a JSON Schema for your own model and validates its reply before the grid applies it (`getGridAiSchema`, `validateGridAiState`). No AI SDK, no row data sent by default.
- **AI Toolkit** (v3.4–3.5): `@opencorestack/opengridx/ai` turns your columns into a JSON Schema for your own model and validates its reply before the grid applies it (`getGridAiSchema`, `validateGridAiState`); `aiAssistant` adds an **Ask AI panel** whose changes show as removable chips with one-click undo, and `createGridAgentTools` gives agents typed tools. No AI SDK, no row data sent by default.
- **Internationalisation** (v3.9): every built-in string (menus, toolbar, panels, AI panel, pagination, screen-reader announcements) is a `localeText` key, and **right-to-left** layouts work with `dir="rtl"` (v3.6), including pinned columns.
- **Accessibility**: WCAG 2.1 AA — full ARIA roles and keyboard navigation.
- **Theming**: CSS variable API, `DataGridThemeProvider` with 5 built-in themes (`darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`) and custom themes.
- **Theming**: CSS variable API, `DataGridThemeProvider` with 5 built-in themes (`darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`) and custom themes; the demo's **Theme Builder** edits every theme key live and exports the result as code.
- **Column & Row Reordering**: Drag-and-drop column reordering and row reordering.
- **Inline Cell Editing**: Full inline editing with `processRowUpdate` validation.
- **State Persistence**: Save and restore grid state (sort, filter, pagination, columns, density) via `useGridStateStorage`.
Expand Down Expand Up @@ -120,7 +122,7 @@ This means that **Cursor**, **GitHub Copilot**, **Windsurf**, and other AI agent
| `rowHeight` | `number` | `52` | Row height in pixels. |
| `headerHeight` | `number` | `56` | Header height in pixels. |
| `noRowsLabel` | `string` | `'No Data'` | Custom empty-state message. |
| `localeText` | `GridLocaleText` | — | Overrides for the pager and empty-state strings (i18n). |
| `localeText` | `GridLocaleText` | — | Overrides for every built-in UI string (v3.9; pagination and the empty state before). |
| `ariaLabel` | `string` | — | Accessible name for the grid. |
| `initialState` | `GridInitialState` | — | Starting sort, filter, pagination, column and density state (uncontrolled). |
| `onStateChange` | `(state: GridState) => void` | — | Fires when sort, filter, pagination, columns or density change. |
Expand Down Expand Up @@ -490,12 +492,14 @@ Built-in themes: `darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compac

| Artifact | Size | Gzipped | Notes |
| :--- | :--- | :--- | :--- |
| **Core ES Module** (`opengridx.es.js`) | 331 KB | **86 KB** | Use this — tree-shakeable |
| **Core UMD** (`opengridx.umd.js`) | 233 KB | **73 KB** | CommonJS / CDN compat |
| **Styles** (`opengridx.css`) | 65 KB | **10 KB** | `import '@opencorestack/opengridx/styles'` |
| **What an app ships for `DataGrid`** (your bundler minifies) | 271 KB | **87 KB** | Measured v3.9.0: esbuild, minified, React external, `DataGrid` only |
| **Core ES Module** (`opengridx.es.js`, as published) | 493 KB | 130 KB | Unminified with readable names; use this — tree-shakeable |
| **Core UMD** (`opengridx.umd.js`) | 357 KB | 112 KB | CommonJS / CDN compat |
| **Styles** (`opengridx.css`) | 81 KB | **12 KB** | `import '@opencorestack/opengridx/styles'` |
| **AI toolkit** (`/ai`, optional import) | 20 KB | **7 KB** | Adds nothing unless imported |
| **ExcelJS** (optional peer dep) | — | — | `npm install exceljs` |
| **jsPDF** (optional peer dep) | — | — | `npm install jspdf jspdf-autotable` |
| **npm package download** | — | **~1.1 MB** | Compressed tarball (includes `lib/` source, docs and source maps) |
| **npm package download** | — | **~2.5 MB** | Compressed tarball (includes `lib/` source, docs and source maps; 7.8 MB unpacked) |

- **Tree-shaking Ready**: ES Module build — bundlers (Vite, Webpack) only include what you use.
- **Zero UI Dependencies**: No MUI, Ant Design, or Radix. Pure React + vanilla CSS.
Expand Down
3 changes: 2 additions & 1 deletion demo/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './Home.css';
import { version as packageVersion } from '../package.json';

interface HomeProps {
onNavigate?: (id: string) => void;
Expand Down Expand Up @@ -63,7 +64,7 @@ export default function Home(_props: HomeProps) {
<div className="home-logo-hero">
<img src={`${import.meta.env.BASE_URL}banner.png`} alt="OpenGridX Logo" className="home-banner-image" />
</div>
<span className="home-badge">OpenGridX v3.7.0</span>
<span className="home-badge">OpenGridX v{packageVersion}</span>
<p className="home-subtitle">
The elite, high-performance DataGrid for modern React.
Built to handle massive data with a premium developer experience.
Expand Down
2 changes: 1 addition & 1 deletion demo/public/llms.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenGridX — AI Agent Context
# Package: @opencorestack/opengridx
# Version: 3.9.0
# Version: 3.9.1
# Docs: https://opencorestack.github.io/OpenGridX/
# This file is machine-readable. AI developers: start here.

Expand Down
6 changes: 4 additions & 2 deletions docs/architecture/datagrid-orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
`GridToolbarSlot`, `GridAiAssistantArea` (the `aiAssistant` panel or `slots.aiAssistantPanel` while open, anchored under the toolbar), `GridStandaloneColumnPanel`, `GridListView` (list view) or `GridViewport` (`components/DataGrid/GridViewport.tsx`, the `role="grid"` viewport:
`Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom,
`GridAggregationFooter`), `GridCellSelectionStatsArea` (`showCellSelectionStats` with `cellSelection`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`,
`GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` builds the overlay slot elements and passes the
`GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` also provides `GridLocaleContext` (v3.9) around all of them: `localeText`, kept stable while shallowly equal (`useShallowStableValue`), resolved once by `resolveGridLocale` (`lib/utils/gridLocale.ts`); components and `Row` read it with `useGridLocale()` instead of a prop, so `Row` gets no prop that changes per render. `GridRoot` builds the overlay slot elements and passes the
rest of its props on to `GridViewport`. The props all three row renderers take alike are built once in
`GridViewport` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's
render rectangle; each row renderer turns it into a per-row `cellRange` (`getRowCellRange`), `null` for rows
outside it, and `Row` is memoised with a value comparison of that prop, so a drag only re-renders the rows the
range enters or leaves. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill
range enters or leaves. The same rule holds for every row prop (v3.9): `editingCell` goes only to the row being
edited, `focusedCellField` only to the focused row, and the row click and checkbox handlers keep one identity
(`useGridRowInteractions` reads the latest handlers when called). `DataGrid.rowMemo.test.tsx` guards it. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill
handle (a single selected cell gets an empty column interval, so it draws the handle but no range).
2 changes: 2 additions & 0 deletions docs/architecture/use-grid-virtualization.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Row heights include an expanded detail panel's height, so the prefix sums accoun

Columns use the same search on `unpinnedAccWidths` (also inclusive), relative to the left edge of the unpinned area (`scrollLeft - leftPinnedWidth`), widened by 6 columns on each side. The skipped widths become `__spacer_left__` / `__spacer_right__` entries (`isSpacer: true`) in `virtualColumns`.

The column window is computed in its own `useMemo` (v3.9), over the column parts of the layout (`unpinnedAccWidths`, `unpinnedColsWithWidth`, the pinned column lists and widths), `scrollLeft` and the viewport width only. A row change (an edit), a vertical scroll or a row-height change therefore keeps the same `virtualColumns` array, and `Row`, which is memoised on its props, does not re-render for it. `useLayout` returns the row and column parts from separate memos (v3.7.1), so the column fields keep their identity while rows change.

When a column declares `colSpan` / `rowSpan`, `DataGrid` widens this window afterwards (`useGridSpanRowWindow` / `useGridSpanColumnWindow`) so a span whose origin is outside the window is not cut.

---
Expand Down
2 changes: 1 addition & 1 deletion docs/components/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ You can replace the internal pagination UI using the `pagination` slot:

## 🌍 Internationalisation

Pass `localeText` on `<DataGrid />` to override the text labels in the Pagination component (rows-per-page, range, and page-counter labels). The rows-per-page select takes its accessible name from the `paginationRowsPerPage` label. The navigation landmark label (`aria-label="Pagination"`) and the button labels and titles (`Go to next page`, `Next page`, …) are hardcoded and not overrideable via `localeText`.
Pass `localeText` on `<DataGrid />` to override the text labels in the Pagination component (rows-per-page, range, and page-counter labels). The rows-per-page select takes its accessible name from the `paginationRowsPerPage` label. Since v3.9 the navigation landmark label (`paginationLabel`, default `"Pagination"`) and each button's label and tooltip (`paginationNextPageLabel` / `paginationNextPageTooltip` → `"Go to next page"` / `"Next page"`, and the same for First, Previous and Last) are `localeText` keys as well.

| Key | Type | Default |
| :--- | :--- | :--- |
Expand Down
2 changes: 1 addition & 1 deletion docs/features/rtl.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The column menu is labelled by screen side: in RTL *Pin to Left* adds the column

## Known limitations

- **Menu and panel labels are hard-coded English** ("Pin to Left", "Sort Ascending"); there is no `localeText` key for the column menu yet.
- ~~Menu and panel labels are hard-coded English~~: since v3.9 every built-in string, including the column menu (`columnMenuPinLeft`, `columnMenuSortAscending`, …), is a `localeText` key. See *Locale text for every UI string* in [`API_REFERENCE.md`](../API_REFERENCE.md).
- **Header filter operator symbols** are mirrored by the browser's bidi rules (`∋` shows as `∈`), as in any RTL text.
- **`direction` set through CSS** (a class or stylesheet, not the `dir` attribute) and changed at runtime is not detected; scroll the grid or remount it with a `key`.
- **Headless Chromium tests:** Playwright launches Chromium with `--hide-scrollbars`. With classic scrollbars (Linux) `scrollbar-gutter: stable` still reserves the gutter, and in RTL Chromium then cannot scroll the last gutter-width of the start into view (`scrollLeft` stops at minus the gutter). A plain scroller without the grid does the same and real browsers with visible scrollbars do not. Tests must compare with the browser's scroll origin, as `DataGrid.rtl.browser.test.tsx` does. To reproduce on macOS: `defaults write chrome-headless-shell AppleShowScrollBars Always` (undo with `defaults delete chrome-headless-shell`).
Expand Down
Loading
Loading