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

data-griddle

v0.1.0

Published

A virtualized React data grid with editing, frozen columns and customizable styles.

Readme

Data Griddle - A performant React Data Grid

A virtualized, DOM-based data grid for React. The component supports large datasets while keeping selection, editing, and pointer interactions off the main cell-rendering path.

Features

  • Virtualized rows and center columns
  • Left and right frozen columns
  • Cell focus, range selection, and checkbox row selection
  • Touch hold-and-drag range selection with edge scrolling
  • Touch double-tap entry into cell editing
  • Keyboard navigation and type-to-edit
  • Custom cell, header, editor, and selection-checkbox renderers
  • Stable-ID imperative cell focus
  • Synchronous validation and asynchronous commits
  • Mouse and touch within-zone column reordering (hold a header, drag, release)
  • Mouse and touch column resizing with optional persistence callbacks

Usage

The initial release target is 0.1.0, an experimental 0.x release. APIs, styling contracts and behavior may change; pin an exact version and review release notes before upgrading. The command below is for the npm release; until it is published, use the local tarball workflow in the package verification guide.

npm install --save-exact [email protected]

Requires matching React and React DOM versions satisfying ^18.3.1 || ^19.2.6. The package is ESM-only and targets ES2022; CommonJS is not provided. Packed consumers have been tested with React 18.3.1 and 19.2.6 in Vite, Next.js Pages Router, and React 19 in Next.js App Router. These checks use desktop Chromium; they do not establish minimum browser/OS versions or full mobile and screen-reader support.

Import data-griddle/styles.css once in your application entry. The JavaScript entry does not automatically inject CSS. In Next.js use the root layout (App Router) or pages/_app.tsx.

"use client";

import "data-griddle/styles.css";

import { useState } from "react";
import { DataGrid } from "data-griddle";

import type { CellCommit, Column } from "data-griddle";

type Person = {
  id: number;
  name: string;
  role: string;
};

const columns: Column<Person>[] = [
  {
    id: "name",
    name: "Name",
    width: 220,
    frozen: "left",
    accessor: (row) => row.name,
    editable: true,
  },
  {
    id: "role",
    name: "Role",
    width: 180,
    accessor: (row) => row.role,
    editable: true,
  },
];

export function PeopleGrid() {
  const [rows, setRows] = useState<Person[]>([
    { id: 1, name: "Ada", role: "Engineer" },
  ]);
  const [columnOrder, setColumnOrder] = useState(
    columns.map((column) => column.id)
  );

  const commit = ({ rowId, columnId, nextValue }: CellCommit<Person>) => {
    setRows((current) =>
      current.map((row) =>
        row.id === rowId ? { ...row, [columnId]: nextValue } : row
      )
    );
  };

  return (
    <div style={{ height: 480 }}>
      <DataGrid
        rows={rows}
        columns={columns}
        getRowId={(row) => row.id}
        columnOrder={columnOrder}
        onColumnOrderChange={setColumnOrder}
        onCellCommit={commit}
        enableRowSelection
      />
    </div>
  );
}

The grid fills its parent, so its container must have a defined height. Row data remains owned by the caller; commit handlers must update rows with accepted values.

Loading and empty results

Pass application-owned request activity through loading (default false). The required rows prop accepts null and undefined for an unavailable result; an empty array means a completed result with no rows. Preserve that distinction instead of replacing missing data with [].

<DataGrid
  rows={data}
  loading={isFetching}
  columns={columns}
  getRowId={(row) => row.id}
  aria-label="People"
  loadingLabel="Updating people"
  emptyContent={<span>No matching people</span>}
/>

Here data and isFetching come from your application. The grid does not fetch or cache data.

| Rows | Loading | Display | | -------------------- | ------- | ---------------------------------------------------------------------------- | | null / undefined | true | Headers and skeleton rows, without a spinner | | null / undefined | false | Headers and a blank body; the app handles errors or instructions | | Any array | true | Retained result beneath an overlay covering headers and body, with a spinner | | [] | false | Empty content, defaulting to “No rows” | | Populated array | false | Normal grid |

Keep supplying previous rows during sorting, filtering, or pagination requests to retain them under the overlay. Passing missing rows removes that result and shows the initial skeleton while loading. Initial skeleton bars gently pulse to show activity. The existing skeleton fallback for virtualized rendering gaps stays static and works independently of loading.

loadingIndicator replaces only the refresh indicator; the grid owns overlay placement and behavior. emptyContent replaces empty-result content. Explicit null hides either visual. Keep custom indicators noninteractive; loadingLabel supplies the accessible announcement independently. The default spinner always rotates, including when reduced motion is enabled. Custom indicators control their own animation behavior.

Loading blocks pointer and keyboard interactions inside the grid, including custom cell/header controls and the editor portal. Outside controls remain usable. Active resize/reorder gestures are cancelled without committing, selection stops at its current range, and pending saves finish normally. An active editor is paused with its draft intact and resumes against the same row ID. If the edited row or column disappears, a notice preserves the draft until its target returns or you explicitly discard it. Focus is restored after a background refresh unless you moved it to another control. As usual, an outside click before loading begins may commit an edit.

id, className, and style apply to the stationary outer frame; accessible naming applies to the focusable inner scroller. Code that previously used the root element as the scroll container must account for this new wrapper. Loading styles are included in the exported stylesheet. Customize their colors through CSS variables on the frame:

.my-grid {
  --dgr-loading-overlay-background: rgb(255 255 255 / 65%);
  --dgr-loading-indicator-color: #57534e;
  --dgr-empty-color: #78716c;
}

The packed-consumer fixture exercises initial loading, refresh and empty results. Custom indicators and delayed refresh while editing need a dedicated consumer scenario; the local app is a basic grid.

Column behavior

Explicit column widths are preserved, even when they leave unused space. The last column in visual order without a width or a width override fills the remaining viewport width; earlier unspecified columns use the 140px default. The available space excludes the checkbox gutter and all other columns, including frozen columns. Automatic sizing respects minWidth (48px by default) and maxWidth: insufficient room causes horizontal scrolling, while a maximum can leave unused space.

Manually resizing an automatic column gives it an explicit width for the session. The last remaining unspecified column then fills the space, if there is one. Controlled columnWidths and defaultColumnWidths also count as explicit widths. Removing a controlled override restores the column's eligibility for automatic sizing when its schema has no width. Container-driven sizing does not emit onColumnWidthsChange.

  • accessor reads the displayed value from a row.
  • editable enables editing; renderEditor can provide a custom editor.
  • renderCell and renderHeader customize read-mode cells and headers.
  • parseValue converts a draft before validation and commit.
  • validate returns an error message to reject a value.
  • onCommit overrides the grid-level onCellCommit for one column.
  • frozen pins a column to the left or right zone.
  • selectable, resizable, reorderable, and reorderBarrier customize normal-column behavior.
  • type: 'action' is always non-selectable, non-editable, non-resizable, non-reorderable, and a reorder barrier; explicit capability props cannot override those invariants.

Row selection, column order, and column widths support controlled and uncontrolled use through value/defaultValue/onChange-style prop groups. Reordering and resizing work internally by default. A controlled value without its change callback is read-only and its matching affordances are disabled.

Frozen columns are always rendered rather than horizontally virtualized. Keep the number frozen on each side small for large grids.

Development

pnpm install --frozen-lockfile
pnpm dev              # library watch + single-grid Next.js app
pnpm test             # source regressions
pnpm lint             # ESLint + library/app/fixture types
pnpm build            # ESM library + static single-grid app
pnpm check:package    # build + artifact contract
pnpm exec playwright install chromium
pnpm check:consumers  # real tarballs in isolated Vite/Next consumers
pnpm check:touch      # Chromium touch gestures; requires pnpm dev in another terminal

See INTERNALS.md for the rendering model and performance constraints.

For phone testing through ngrok, put the tunnel hostname (without https:// or a port) in apps/docs/.env.local, then restart pnpm dev:

DEV_TUNNEL_HOST=your-tunnel.ngrok-free.dev

The development app uses this hostname in Next.js allowedDevOrigins so its dev WebSocket can connect through the tunnel. Without it, the page can remain on its server-rendered skeleton. Update the value if your tunnel hostname changes. The local environment file is Git-ignored.

Styling and themes

Default styles are scoped to grid-owned classes; import data-griddle/styles.css. Set --dgr-* variables on the frame or an ancestor; active body-mounted editors copy their originating grid's theme. Columns accept cellClassName and headerClassName as strings or context callbacks. Cells and headers clip overflow without automatic ellipsis. Opt into truncation with application CSS through these classes, or style individual labels and links inside custom renderers. Use rowHeight and column widths for geometry; CSS controls appearance.

See the styling contract for tokens, defaults, parts, callback contexts, and portal synchronization limits. The guide includes theme and conditional-class examples; the local app does not include an interactive theme showcase.

Integration APIs

See the integration guide for controlled/uncontrolled/read-only state, stable identity, editing and persistence, custom selection controls, and the DataGridHandle.focusCell command. Run pnpm dev and open / for the basic editable grid. Packed-consumer fixtures cover additional public-API scenarios; their setup and coverage are described in the verification guide below.

Stores and geometry helpers remain private. See package verification for exact tested versions and remaining limitations.

Accessibility and interaction

See keyboard, touch and accessibility guidance for navigation, native control tab order, editor behavior, remaining touch/column interaction gaps and the manual test matrix. Source semantics and keyboard navigation are implemented; target-browser, physical-device and screen-reader support remains unverified. Applications must adapt frozen widths and columns to narrow containers; the basic app does not demonstrate responsive unpinning.

Workspace and releases

  • packages/data-griddle/: canonical source, styles, tests, ESM build and declarations.
  • apps/docs/: a single-grid Next.js App Router development app.
  • fixtures/: isolated packed consumers, copied outside the workspace by verification scripts.
  • docs/: repository guides, read directly as Markdown.

The development app consumes only public package exports. pnpm dev builds the library first and watches it alongside Next.js; restart it after changing the public export map. No Turborepo or Nx is needed.

The initial release uses version 0.1.0 and the npm latest tag. The tag controls the default installation; it does not imply a stable API. Pin preview versions exactly. Documentation lives in this README and the repository guides; a dedicated documentation site is outside this release.

See the release checklist for preparation status, remaining verification and the manual publishing procedure, and release notes for the initial feature scope and limitations. The library is configured for publication; the workspace and development app remain private. There is no automated publishing workflow.