npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gm-datagrid

v0.1.23

Published

Enterprise Excel-like data grid with Rust/Wasm engine, featuring inline-aligned group subtotals and automatic group aggregate preloading

Downloads

643

Readme

gm-datagrid

Enterprise-grade, Excel-like data grid for React — powered by a Rust/WebAssembly core engine.

  • ⚡ Handles 100 000+ rows at 60 FPS with canvas-free virtualization
  • 🦥 Sorting, filtering, and Excel export run inside Rust/Wasm — off the main thread
  • 📋 Excel-compatible clipboard (Ctrl+C / Ctrl+V TSV)
  • ✏️ Inline cell editing with async validation and automatic revert on error
  • 📊 Column subtotals updated live as filters change
  • 🔢 Inline Group Aggregations — column-aligned aggregates visible directly in group headers (even when collapsed)
  • 🔢 Aggregation footer rowsum, avg, min, max, size per column; MUI DataGrid Premium compatible aggregationModel API
  • 📅 Date filter operators — is / is not / is after / is before / is empty / is not empty
  • 📥 Multi-format export — toolbar picker for xlsx (styled, freeze pane, zebra striping via ExcelJS), csv, txt, json
  • 🌐 Built-in i18nlocale="es" / locale="en" with full label overrides
  • 🔒 Feature flags — selectively disable export, filters, sort, or grouping per instance
  • 🧹 Clear all filters button in toolbar (desktop + mobile)
  • 🎨 Built on MUI — inherits your existing theme automatically

Installation

# npm
npm install gm-datagrid

# pnpm
pnpm add gm-datagrid

# yarn
yarn add gm-datagrid

Peer dependencies

npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styled

Quick start

import { GMDatagrid, ColumnDataTypesEnum } from 'gm-datagrid'

const columns = [
  { field: 'id',    headerName: 'ID',    type: ColumnDataTypesEnum.ID },
  { field: 'name',  headerName: 'Name',  type: ColumnDataTypesEnum.STRING, sortable: true },
  { field: 'price', headerName: 'Price', type: ColumnDataTypesEnum.PRICE, calculateSubTotal: true },
  { field: 'stock', headerName: 'Stock', type: ColumnDataTypesEnum.INT },
]

async function fetchProducts(params) {
  const res = await fetch('/api/products?' + new URLSearchParams(params))
  const data = await res.json()
  return { success: true, data }
}

export default function ProductsPage() {
  return (
    <GMDatagrid
      title="Products"
      columns={columns}
      sourceApi={fetchProducts}
      apiParameters={{ active: true }}
    />
  )
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | title | string | required | Grid heading shown in the toolbar | | columns | ColumnDef[] | required | Column definitions (see below) | | sourceApi | (params) => Promise<{ success, data }> | — | Async data fetcher. Called on mount and after every refresh() | | apiParameters | object | {} | Extra params merged into every sourceApi call | | localData | Row[] | — | Static data — skips sourceApi entirely | | apiContext | ApiContext | — | Injected into every sourceApi call (e.g. companyId, authToken) | | processRowUpdate | (newRow, oldRow) => Row \| Promise<Row> | — | Called when a cell is saved. Throw to reject and revert | | quickFilter | boolean | true | Show / hide the global search input in the toolbar | | quickFilterPlaceholder | string | — | Placeholder text for the quick-filter input | | rowColorGetter | (row) => { backgroundColor?, color? } | — | Per-row color highlights (e.g. overdue rows in red) | | onRowDetail | (row) => void | — | Called when the user opens a row detail (double-click or menu action) | | checkboxSelection | boolean | false | Prepend a checkbox column for row selection | | onRowSelectionChange | (rows: Row[]) => void | — | Fires on every selection change with the full array of selected rows | | locale | 'es' \| 'en' | 'es' | Built-in UI language. See Internationalisation |

Pagination props

| Prop | Type | Default | Description | |---|---|---|---| | paginationMode | 'client' \| 'server' | 'client' | Client-side (engine) or server-side pagination | | paginationModel | { page: number; pageSize: number } | { page: 0, pageSize: 25 } | Controlled pagination state | | onPaginationModelChange | (model) => void | — | Called when the user changes page or page size | | pageSizeOptions | number[] | [25, 50, 100] | Rows-per-page options shown in the footer selector | | rowCount | number | — | Total row count for server-side pagination |

Feature flags

Control which toolbar actions and column interactions are available without touching individual column definitions.

| Prop | Type | Default | Description | |---|---|---|---| | exportable | boolean | true | Show / hide the Export button in the toolbar | | exportFormats | ExportFormat[] | ['xlsx', 'csv'] | Which formats appear in the export menu. A single format skips the menu and downloads directly | | exportableColumns | string[] | — | Whitelist of field values included in the .xlsx export. Omit to export all visible columns | | filterable | boolean | true | Show / hide the filter panel toggle button and the per-column filter row | | filterableColumns | string[] | — | Only these fields render a filter input in the filter panel | | sortable | boolean | true | Enable / disable sort on all column headers globally | | sortableColumns | string[] | — | Only these fields respond to header click-to-sort. Overrides per-column sortable | | groupable | boolean | false | Show / hide the group-by picker in the toolbar | | groupableColumns | string[] | — | Fields offered in the group-by picker. Falls back to all columns when omitted | | editable | boolean | false | Enable / disable inline editing globally | | editableColumns | string[] | — | Only these fields are editable (overrides per-column editable flag) | | aggregable | boolean | false | Enable the aggregation footer row. Off by default — zero overhead when unused | | aggregationModel | AggregationModel | — | Controlled { field: fn } map (see Aggregation) | | onAggregationModelChange | (model: AggregationModel) => void | — | Fired when the aggregation model changes | | initialState | { aggregation?: { model: AggregationModel } } | — | Uncontrolled seed — mirrors MUI DataGrid Premium initialState.aggregation.model |

Example — read-only reporting grid with restricted export:

<GMDatagrid
  title="Sales Report"
  columns={columns}
  sourceApi={fetchSales}
  sortable={false}           // no sort at all
  filterable              // filters on (default)
  filterableColumns={['customer', 'status', 'date']}
  exportable
  exportableColumns={['customer', 'total', 'date']}  // exclude internal fields
  groupable
  groupableColumns={['customer', 'status']}
/>

Per-column flags still apply. If you set sortable={true} globally but a column has sortable: false in its ColumnDef, that column remains non-sortable. The whitelist (sortableColumns) is intersected with the per-column flag — a field must pass both to be interactive.


Column definition (ColumnDef)

interface ColumnDef {
  field: string              // key in the row object
  headerName?: string        // display label (falls back to field)
  type: ColumnDataTypesEnum  // controls rendering, formatting and export
  width?: number             // initial width in px
  minWidth?: number          // minimum resize width in px
  flex?: number              // flex-grow factor (like CSS flex)
  hide?: boolean             // exclude column from render
  pin?: 'left' | 'right'    // sticky pinned columns
  sortable?: boolean         // enable header click to sort
  filterable?: boolean       // include in filter panel
  editable?: boolean         // enable inline cell editing
  calculateSubTotal?: boolean // show live aggregate in header; default fn chosen by column type
  aggregable?: boolean        // set false to exclude from aggregation footer
  availableAggregationFunctions?: AggregationFunction[]  // restrict fn picker per column
  renderCell?: (params: CellRenderParams) => ReactNode  // custom renderer
  valueGetter?: (row: Row) => unknown   // transform raw value before render
  valueFormatter?: (value: unknown) => string           // format to display string
  statusMap?: Record<string, { label: string; color: string }> // for `status` type
}

Column types (ColumnDataTypesEnum)

| Value | Renders as | Notes | |---|---|---| | STRING | Plain text | Default | | NUMBER | 1,234 | Right-aligned | | PRICE | 1,234.0000 | 4 decimal places, right-aligned | | TOTAL | 1,234.00 | 2 decimal places, right-aligned | | DISCOUNT | Progress bar | 0–100 range | | PERCENT | Progress bar | 0–100 range | | TAX | Progress bar | 0–100 range | | MARGIN | Progress bar | 0–100 range | | DATE | DD/MM/YYYY | Date operator filter panel | | DATE_TIME | DD/MM/YYYY HH:mm:ss | Date operator filter panel | | STATUS | Coloured chip | Use statusMap to configure labels/colors | | LABELS | Array of chips | Cell value must be string[] | | IMAGE | 40 px avatar | Cell value is a URL | | ACTION | Custom renderer | Provide renderCell | | MENU_ACTION | Icon button + menu | Triggers onRowDetail | | ID | Plain text | | | INT | 1,234 | Integer, right-aligned | | CODE | Plain text | Monospace hint | | ARRAY | Comma-joined | Cell value is string[] | | TEXT | Plain text | Multi-line friendly |


Ref API (imperative handle)

Use GMGridContainer when you need to refresh the grid or update its parameters from outside:

import { useRef } from 'react'
import { GMGridContainer } from 'gm-datagrid'
import type { GMGridContainerRef } from 'gm-datagrid'

export default function OrdersPage() {
  const gridRef = useRef<GMGridContainerRef>(null)

  return (
    <>
      <button onClick={() => gridRef.current?.refresh()}>Refresh</button>
      <button onClick={() => gridRef.current?.setParams({ status: 'PENDING' })}>
        Show pending
      </button>

      <GMGridContainer
        ref={gridRef}
        title="Orders"
        columns={columns}
        sourceApi={fetchOrders}
      />
    </>
  )
}

| Method | Signature | Description | |---|---|---| | refresh() | () => void | Re-fetches data with the current params | | setParams(p) | (params: object) => void | Replaces apiParameters and re-fetches |

GMDatagrid also supports a ref with an extended handle:

import { useRef } from 'react'
import { GMDatagrid } from 'gm-datagrid'
import type { GMDatagridRef } from 'gm-datagrid'

const ref = useRef<GMDatagridRef>(null)

// Programmatic row selection
ref.current?.getSelectedRows()           // Row[]
ref.current?.clearSelection()
ref.current?.setSelectedIds(['id1', 'id2'])
ref.current?.refresh()

Context hook (useGMGrid)

Access the grid state from any child component rendered inside GMDatagrid:

import { useGMGrid } from 'gm-datagrid'

function OrderSummary() {
  const { data, loading, totalCount } = useGMGrid()

  if (loading) return null
  return <p>{totalCount} orders loaded</p>
}

| Field | Type | Description | |---|---|---| | data | Row[] | All rows currently in the dataset | | loading | boolean | true while sourceApi is in flight | | totalCount | number | Total filtered row count (for pagination display) | | handleRefresh | () => void | Trigger a manual refresh | | selectedIds | Set<string> | IDs of currently selected rows |


Pagination

The grid renders a footer with page pills and a rows-per-page selector.

Client-side (default)

The Wasm engine slices rows in memory — no extra API calls on page change:

<GMDatagrid
  title="Products"
  columns={columns}
  sourceApi={fetchProducts}
  pageSizeOptions={[10, 25, 50]}
/>

Server-side

Control pagination yourself and pass the total row count:

const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 25 })

async function fetchPage(params) {
  const { page, pageSize } = params
  const res = await api.get('/products', { page, pageSize })
  return { success: true, data: res.rows, total: res.total }
}

<GMDatagrid
  title="Products"
  columns={columns}
  sourceApi={fetchPage}
  paginationMode="server"
  paginationModel={paginationModel}
  onPaginationModelChange={setPaginationModel}
  rowCount={totalFromServer}
  pageSizeOptions={[25, 50, 100]}
/>

Row selection

Enable checkboxes and respond to selection changes:

const [selected, setSelected] = useState([])

<GMDatagrid
  title="Invoices"
  columns={columns}
  sourceApi={fetchInvoices}
  checkboxSelection
  onRowSelectionChange={setSelected}
/>

// Bulk action button
<button disabled={selected.length === 0} onClick={() => bulkDelete(selected)}>
  Delete {selected.length} rows
</button>

Programmatic control via ref:

const ref = useRef<GMDatagridRef>(null)

ref.current?.getSelectedRows()           // get current selection
ref.current?.setSelectedIds(['a', 'b'])  // select programmatically
ref.current?.clearSelection()            // deselect all

Date filter operators

DATE and DATE_TIME columns show an operator dropdown in the filter panel:

| Operator | Description | |---|---| | is | Exact date match | | is not | Excludes the selected date | | is after | Strictly after the selected date | | is on or after | On or after the selected date | | is before | Strictly before the selected date | | is on or before | On or before the selected date | | is empty | Cell is null or empty — no calendar shown | | is not empty | Cell has any value — no calendar shown |

The filter value is represented as DateFilterValue:

import type { DateFilterValue, DateFilterOp } from 'gm-datagrid'

// { op: 'gt', value: '2026-01-01' }
// { op: 'isNull', value: '' }

Inline editing

Enable editable: true on a column and provide processRowUpdate:

async function saveRow(newRow, oldRow) {
  const res = await fetch(`/api/products/${newRow.id}`, {
    method: 'PATCH',
    body: JSON.stringify(newRow),
  })
  if (!res.ok) throw new Error('Save failed')
  return newRow
}

<GMDatagrid
  title="Products"
  columns={[
    { field: 'name',  type: ColumnDataTypesEnum.STRING, editable: true },
    { field: 'price', type: ColumnDataTypesEnum.PRICE,  editable: true },
  ]}
  sourceApi={fetchProducts}
  processRowUpdate={saveRow}
/>

Keyboard shortcuts while editing:

| Key | Action | |---|---| | Any printable key | Enter edit mode, replace value | | F2 | Enter edit mode, keep current value | | Enter | Save and move to the next row | | Tab / Shift+Tab | Save and move right / left | | Escape | Discard and exit edit mode | | Arrow keys | Move focused cell (view mode only) | | Ctrl+Arrow / Meta+Arrow | Passed through to the browser — never hijacked |

Focus-scoped keyboard handling — all grid keyboard shortcuts are active only when focus is inside the grid. Arrow keys, Shift+Arrow, Ctrl+Arrow, and clipboard shortcuts (Ctrl+C / Ctrl+V) in other inputs or text areas on the same page are never intercepted by the grid.


Custom cell renderer

import { ColumnDataTypesEnum } from 'gm-datagrid'

const columns = [
  {
    field: 'status',
    type: ColumnDataTypesEnum.ACTION,
    renderCell: ({ row, value }) => (
      <span style={{ color: value === 'ACTIVE' ? 'green' : 'red' }}>
        {String(value)}
      </span>
    ),
  },
]

The renderCell function receives:

| Param | Type | Description | |---|---|---| | row | Row | The full row object | | field | string | The column field name | | value | unknown | The (possibly valueGetter-transformed) cell value | | colDef | ColumnDef | The full column definition |


Row colour highlights

<GMDatagrid
  title="Invoices"
  columns={columns}
  sourceApi={fetchInvoices}
  rowColorGetter={(row) => {
    if (row.overdue) return { backgroundColor: '#fff3f3', color: '#c62828' }
    if (row.status === 'PAID') return { backgroundColor: '#f3fff5' }
    return {}
  }}
/>

Multi-tenant / authenticated APIs

Use apiContext to inject auth tokens or tenant IDs without modifying each sourceApi:

import { useSession } from './contexts/SessionContext'

function App() {
  const { token, companyId } = useSession()

  return (
    <GMDatagrid
      title="Customers"
      columns={columns}
      sourceApi={fetchCustomers}
      apiContext={{ authToken: token, companyId }}
    />
  )
}

Multi-format export

By default the toolbar shows an export button that downloads both .xlsx and .csv. When more than one format is enabled a format picker menu opens on click:

<GMDatagrid
  title="Products"
  columns={columns}
  sourceApi={fetchProducts}
  exportFormats={['xlsx', 'csv', 'txt', 'json']} // all four formats
/>

// xlsx only — no menu, direct download
<GMDatagrid exportFormats={['xlsx']} ... />

// Disable export entirely
<GMDatagrid exportable={false} ... />

Supported formats

| Format | Extension | Notes | |---|---|---| | xlsx | .xlsx | Client-side via ExcelJS — styled headers, freeze pane, zebra striping, native number/date cell types | | csv | .csv | RFC 4180, UTF-8 BOM — opens correctly in Excel | | txt | .txt | Tab-separated values — paste directly into Excel | | json | .json | Array of objects keyed by column field |

ACTION, MENU_ACTION, and IMAGE columns are automatically excluded from all exports.

xlsx formatting

The generated .xlsx file includes:

| Feature | Detail | |---|---| | Header row | Bold white text, GStore blue (#251BBC) fill, medium bottom border | | Freeze pane | Header row stays visible while scrolling | | Zebra striping | Alternating light grey rows for readability | | Number formats | #,##0 (NUMBER/INT), #,##0.0000 (PRICE), #,##0.00 (TOTAL), 0.00 (progress types) | | Date formats | Native Excel date cells — dd/mm/yyyy / dd/mm/yyyy hh:mm:ss | | Alignment | Numeric columns are right-aligned | | Column widths | Auto-sized from header + first 200 rows (clamped 8–60 chars) | | valueFormatter | Applied to string/status/label columns — same output as csv/txt |


Subtotals

Set calculateSubTotal: true on any column to display a live aggregate in the column header. The value updates as filters change. No aggregationModel prop is needed — the default aggregation function is chosen automatically based on the column type:

| Column type | Default function | |---|---| | number, price, total, int, discount, percent, tax, margin | sum | | date, dateTime | count-distinct | | boolean / field named active | count-true | | status, labels | count-distinct | | string, text, all others | count |

{ field: 'amount',   type: ColumnDataTypesEnum.TOTAL,  calculateSubTotal: true }  // → sum
{ field: 'date',     type: ColumnDataTypesEnum.DATE,   calculateSubTotal: true }  // → count-distinct
{ field: 'active',   type: ColumnDataTypesEnum.STRING, calculateSubTotal: true }  // → count-true
{ field: 'status',   type: ColumnDataTypesEnum.STATUS, calculateSubTotal: true }  // → count-distinct

You can still override the function or restrict which functions appear in the picker:

{ field: 'price', type: ColumnDataTypesEnum.PRICE, calculateSubTotal: true,
  availableAggregationFunctions: ['avg', 'min', 'max'] }

Aggregation

Show a sticky footer row with computed aggregate values per column. Compatible with MUI DataGrid Premium's aggregationModel API.

Feature flag — disabled by default. Set aggregable to opt in.

Basic usage

<GMDatagrid
  aggregable                           // enable the feature
  aggregationModel={{ total: 'sum', price: 'avg', id: 'size' }}
  columns={columns}
  sourceApi={fetchData}
/>

Controlled model with change callback

const [aggModel, setAggModel] = useState({ total: 'sum' });

<GMDatagrid
  aggregable
  aggregationModel={aggModel}
  onAggregationModelChange={setAggModel}
  ...
/>

Uncontrolled initial state (MUI-compatible)

<GMDatagrid
  aggregable
  initialState={{ aggregation: { model: { total: 'sum', price: 'avg' } } }}
  ...
/>

Per-column control

// Exclude a column from the footer and picker entirely
{ field: 'code', type: ColumnDataTypesEnum.CODE, aggregable: false }

// Restrict which functions appear in the column's picker
{ field: 'price', type: ColumnDataTypesEnum.PRICE, availableAggregationFunctions: ['avg', 'min', 'max'] }

Built-in aggregation functions

| Function | Symbol | Description | |---|---|---| | sum | Σ | Sum of all non-null values | | avg | x̄ | Arithmetic mean of non-null values | | min | min | Smallest value (lexicographic for strings/dates) | | max | max | Largest value (lexicographic for strings/dates) | | size | # | Count of non-null cells | | count | # | Count of non-null cells (alias for size) | | count-distinct | ≠# | Count of unique non-null values | | count-true | ✓ | Count of truthy / "true" / true values | | count-false | ✗ | Count of falsy / "false" / false values |

Interactive function picker

Columns with calculateSubTotal: true show a small pill in the column header with the active function symbol (Σ, , min, max, #, ≠#, , ). Clicking the pill opens a Popover listing the functions available for that column's type (or the availableAggregationFunctions override). Selecting a function updates the model and fires onAggregationModelChange. A Remove aggregation option clears the column from the model.

Group aggregation sub-rows

When both aggregable and groupable are enabled, every expanded group shows a compact sub-row (blue left border, muted background) with per-group aggregate values aligned to their columns.

<GMDatagrid
  aggregable
  groupable
  aggregationModel={{ total: 'sum', price: 'avg' }}
  onAggregationModelChange={setAggModel}
  columns={[
    { field: 'customer', type: ColumnDataTypesEnum.STRING },
    { field: 'total',    type: ColumnDataTypesEnum.TOTAL },
    { field: 'price',    type: ColumnDataTypesEnum.PRICE, availableAggregationFunctions: ['avg', 'min', 'max'] },
    { field: 'code',     type: ColumnDataTypesEnum.CODE,  aggregable: false },
  ]}
  sourceApi={fetchOrders}
/>

Aggregation props

| Prop | Type | Default | Description | |---|---|---|---| | aggregable | boolean | false | Enable the aggregation footer row and group sub-rows | | aggregationModel | AggregationModel | — | Controlled { field: fn } map | | onAggregationModelChange | (model) => void | — | Fired when the model changes (picker or programmatic) | | initialState.aggregation.model | AggregationModel | — | Uncontrolled seed — mirrors MUI DataGrid Premium initialState |


TypeScript

All types are exported from the package root:

import type {
  ColumnDef,
  GMDatagridProps,
  GMDatagridRef,
  GMGridContainerRef,
  Row,
  CellRenderParams,
  CellEditParams,
  ExportFormat,
  AggregationFunction,
  AggregationModel,
  AggregationResults,
  FilterGroup,
  FilterCellValue,
  DateFilterValue,
  DateFilterOp,
  RangeValue,
  SortKey,
  ApiContext,
  GridLabels,
  GridLocale,
  FilterCellLabels,
} from 'gm-datagrid'

import { ColumnDataTypesEnum, isRangeValue, isDateFilterValue } from 'gm-datagrid'

Editing

Per-column editability

Set editable: true on each ColumnDef you want users to edit. The global editable prop and editableColumns whitelist take precedence.

Editing events (MUI DataGrid compatible)

import type { CellEditParams } from 'gm-datagrid'

<GMDatagrid
  title="Products"
  columns={columns}
  sourceApi={fetchProducts}
  editable
  editableColumns={['name', 'price', 'stock']}
  processRowUpdate={async (newRow, oldRow) => {
    await api.patch(`/products/${newRow.id}`, newRow)
    return newRow
  }}
  onCellEditStart={(params: CellEditParams) => {
    console.log('editing started', params.field, params.value)
  }}
  onCellEditStop={(params: CellEditParams) => {
    console.log('editing stopped', params.field)
  }}
  isCellEditable={(params: CellEditParams) => {
    // Only allow editing rows that are in DRAFT status
    return params.row.status === 'DRAFT'
  }}
/>

Programmatic editing via ref

import { useRef } from 'react'
import { GMDatagrid } from 'gm-datagrid'
import type { GMDatagridRef } from 'gm-datagrid'

const ref = useRef<GMDatagridRef>(null)

// Start editing a specific cell
ref.current?.startCellEditMode({ id: 'row-1', field: 'name' })

// Save and exit
ref.current?.stopCellEditMode({ id: 'row-1', field: 'name' })

// Discard changes and exit
ref.current?.stopCellEditMode({ id: 'row-1', field: 'name', ignoreModifications: true })

CellEditParams interface

interface CellEditParams {
  id: string | number   // row id
  field: string         // column field name
  value: unknown        // current cell value
  row: Row              // full row object
}

Editability hierarchy

All four levels must pass for a cell to be editable:

  1. editable prop — global feature flag (false by default)
  2. editableColumns whitelist — if set, field must be in the list
  3. col.editable — per-column flag in ColumnDef
  4. isCellEditable(params) callback — per-row guard

Internationalisation (locale)

All built-in toolbar and filter labels are translated automatically when you set the locale prop.

// Spanish (default)
<GMDatagrid locale="es" title="Productos" columns={columns} sourceApi={fetchProducts} />

// English
<GMDatagrid locale="en" title="Products" columns={columns} sourceApi={fetchProducts} />

Supported locales

| Code | Language | |---|---| | 'es' | Spanish (default) | | 'en' | English |

Custom labels

Need a label that doesn't match either locale, or want to override a single string? Import GridLabels and pass a labels prop directly (takes priority over locale):

import type { GridLabels } from 'gm-datagrid'

const myLabels: Partial<GridLabels> = {
  // Toolbar / filter
  search:       'Rechercher…',
  filterBy:     'Filtrer par colonne',
  hideFilters:  'Masquer les filtres',
  clearFilters: 'Effacer les filtres',
  all: 'Tous',
  yes: 'Oui',
  no:  'Non',
  min: 'Min',
  max: 'Max',
  noGroup: 'Sans regroupement',

  // Pagination
  noRows:      '0 enregistrements',
  rowsRange:   '{from}–{to} sur {total} enregistrements',
  rowsPerPage: 'Lignes par page :',
  firstPage:   'Première page',
  prevPage:    'Page précédente',
  nextPage:    'Page suivante',
  lastPage:    'Dernière page',

  // Row selection
  selectAll:         'Tout sélectionner',
  deselectAll:       'Tout désélectionner',
  selectionSingular: 'élément sélectionné',
  selectionPlural:   'éléments sélectionnés',
  clearSelection:    'Effacer',

  // Groups
  emptyGroup: '(Vide)',

  // Engine error
  engineError: "Impossible d'initialiser le moteur de données",

  // Aggregation
  aggregationFns: {
    sum:  'Somme',
    avg:  'Moyenne',
    min:  'Minimum',
    max:  'Maximum',
    size: 'Nombre',
  },
  aggregationTooltipLabel: '{fn} : {value}',

  // Date filter operators
  dateOps: {
    equals:    'est',
    not:       "n'est pas",
    gt:        'est après',
    gte:       'est le ou après',
    lt:        'est avant',
    lte:       'est le ou avant',
    isNull:    'est vide',
    isNotNull: "n'est pas vide",
  },
}

<GMDatagrid labels={myLabels} title="Produits" columns={columns} sourceApi={fetchProducts} />

All fields are optional — omit any key to fall back to the active locale.

GridLabels reference

| Key | es default | en default | |---|---|---| | search | Buscar… | Search… | | filterBy | Filtrar por columna | Filter by column | | hideFilters | Ocultar filtros | Hide filters | | clearFilters | Limpiar filtros | Clear filters | | noRows | 0 registros | 0 records | | rowsRange | {from}–{to} de {total} registros | {from}–{to} of {total} records | | rowsPerPage | Filas por página: | Rows per page: | | firstPage | Primera página | First page | | prevPage | Página anterior | Previous page | | nextPage | Página siguiente | Next page | | lastPage | Última página | Last page | | selectAll | Seleccionar todo | Select all | | deselectAll | Deseleccionar todo | Deselect all | | selectionSingular | elemento seleccionado | item selected | | selectionPlural | elementos seleccionados | items selected | | clearSelection | Limpiar | Clear | | emptyGroup | (Sin valor) | (Empty) | | engineError | No se pudo inicializar el motor de datos | Could not initialize the data engine | | aggregationFns.sum | Suma | Sum | | aggregationFns.avg | Promedio | Average | | aggregationFns.min | Mínimo | Minimum | | aggregationFns.max | Máximo | Maximum | | aggregationFns.size | Cantidad | Count | | aggregationTooltipLabel | {fn}: {value} | {fn}: {value} |


Changelog

See CHANGELOG.md.


License

MIT