diff --git a/api-extractor.base.json b/api-extractor.base.json index e55ef6b17..ffcb1a68c 100644 --- a/api-extractor.base.json +++ b/api-extractor.base.json @@ -24,6 +24,9 @@ }, "ae-unresolved-link": { "logLevel": "none" + }, + "ae-internal-missing-underscore": { + "logLevel": "none" } } }, diff --git a/apps/website/content/docs/grid/api-reference.mdx b/apps/website/content/docs/grid/api-reference.mdx index 148682147..f4310cf60 100644 --- a/apps/website/content/docs/grid/api-reference.mdx +++ b/apps/website/content/docs/grid/api-reference.mdx @@ -174,7 +174,7 @@ interface SerializeRangesArgs { ## `PretableGrid` — model methods -The `grid` returned by `usePretable` and `usePretableModel`. +The `grid` returned by `usePretable`. | Method | Description | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | @@ -206,7 +206,7 @@ The `grid` returned by `usePretable` and `usePretableModel`. | `setColumnPinned(columnId: string, pinned: "left" \| null): void` | Explicit pin/unpin. Repositions the column to the pin-region boundary. | | `autosizeColumn(columnId: string, options?: AutosizeOptions): void` | Autosize a single column to its measured content width. | | `resetColumnLayout(): void` | Restore order, widths, and pinned state to the original `columns` prop snapshot taken at mount. | -| `mergeColumnsFromProps(nextColumns: PretableColumn[]): void` | Internal: called by `usePretableModel` when the columns prop's id list changes structurally. | +| `mergeColumnsFromProps(nextColumns: PretableColumn[]): void` | Internal: called by `usePretable` when the columns prop's id list changes structurally. | ## `PretableGridSnapshot` @@ -236,7 +236,7 @@ interface PretableGridSnapshot { } ``` -Read via `grid.getSnapshot()` or directly from the `usePretableModel` return value. +Read via `grid.getSnapshot()` or directly from the `usePretable` return value. ## `UsePretableOptions` — `usePretable` arguments @@ -251,10 +251,10 @@ interface UsePretableOptions { `usePretable` returns just the `grid` model. Use it when you only need interaction state, not virtualization. -## `UsePretableModelOptions` — `usePretableModel` arguments +## `UsePretableOptions` — `usePretable` arguments ```ts -interface UsePretableModelOptions extends UsePretableOptions { +interface UsePretableOptions extends UsePretableOptions { viewportHeight: number; viewportWidth?: number; overscan?: number; // default 6 @@ -275,9 +275,9 @@ interface PretableSurfaceState { } ``` -`usePretableModel` returns `{grid, snapshot, renderSnapshot, telemetry}`. Use it when you need virtualization metadata for custom rendering. +`usePretable` returns `{grid, snapshot, renderSnapshot, telemetry}`. Use it when you need virtualization metadata for custom rendering. -## `PretableModel` — `usePretableModel` return +## `PretableModel` — `usePretable` return ```ts interface PretableModel { @@ -344,9 +344,7 @@ Useful for instrumentation (frame budget tracking, visible-row counts, focus sur ```ts function usePretable(opts: UsePretableOptions): PretableGrid; -function usePretableModel( - opts: UsePretableModelOptions, -): PretableModel; +function usePretable(opts: UsePretableOptions): PretableModel; function useResolvedHeights( rowHeightProp?: number, diff --git a/apps/website/content/docs/grid/custom-rendering.mdx b/apps/website/content/docs/grid/custom-rendering.mdx index 6f5013353..d8b409830 100644 --- a/apps/website/content/docs/grid/custom-rendering.mdx +++ b/apps/website/content/docs/grid/custom-rendering.mdx @@ -1,11 +1,11 @@ --- -title: Custom rendering with `usePretableModel` -description: "usePretableModel exposes the engine state so you can render rows your own way." +title: Custom rendering with `usePretable` +description: "usePretable exposes the engine state so you can render rows your own way." nav: Grid order: 9 --- -`usePretableModel` returns the engine's full state: the grid model (with interaction methods), the snapshot (sort, filters, selection, focus), the render snapshot (which rows to render at what positions), and telemetry. You write your own JSX on top, applying the `[data-pretable-*]` attribute contract so `@pretable/ui/grid.css` styles the result. +`usePretable` returns the engine's full state: the grid model (with interaction methods), the snapshot (sort, filters, selection, focus), the render snapshot (which rows to render at what positions), and telemetry. You write your own JSX on top, applying the `[data-pretable-*]` attribute contract so `@pretable/ui/grid.css` styles the result. This page walks through a minimal-but-complete example. @@ -21,9 +21,9 @@ If none of these apply, the [\ drop-in](/docs/grid/pretable-component ## The hook ```ts -import { usePretableModel } from "@pretable/react"; +import { usePretable } from "@pretable/react"; -const { grid, snapshot, renderSnapshot, telemetry } = usePretableModel({ +const { grid, snapshot, renderSnapshot, telemetry } = usePretable({ columns, rows, viewportHeight, @@ -49,7 +49,7 @@ See [API reference](/docs/grid/api-reference) for the full type signatures. ## A minimal working example ```tsx -import { useResolvedHeights, usePretableModel } from "@pretable/react"; +import { useResolvedHeights, usePretable } from "@pretable/react"; import type { PretableColumn, PretableRow } from "@pretable/react"; interface Person extends PretableRow { @@ -69,7 +69,7 @@ export function MyGrid({ rows }: { rows: Person[] }) { const { headerHeight } = useResolvedHeights(); const viewportHeight = 480; - const { grid, snapshot, renderSnapshot } = usePretableModel({ + const { grid, snapshot, renderSnapshot } = usePretable({ columns, rows, viewportHeight, @@ -220,7 +220,7 @@ The minimal example above is a starting point. For production grids, you'll like - **Keyboard navigation** — listen for `ArrowUp` / `ArrowDown` on the viewport and call `grid.moveFocus("up")` / `grid.moveFocus("down")`. The full keyboard contract (shift+arrow extend, Cmd/Ctrl+arrow jump, Tab wrap, Cmd+A, Esc) is wired by `` automatically. - **Pinned columns sticky positioning** — apply `position: sticky; left: ${pinnedOffset}px` to pinned cells. The bench's adapter computes pinned offsets via the same algorithm `` uses internally; see `packages/react-surface/src/rendering.ts` (function `getPinnedLeftOffsets`) for the canonical implementation. -- **Per-row measured heights** — use `useLayoutEffect` to measure rendered row heights and pass `measuredHeights: Record` to `usePretableModel` for content-aware sizing. +- **Per-row measured heights** — use `useLayoutEffect` to measure rendered row heights and pass `measuredHeights: Record` to `usePretable` for content-aware sizing. - **Filter inputs** — render a row of `` elements above the body, debounce changes, and call `grid.setFilter(columnId, value)` per change. - **Telemetry instrumentation** — use the `telemetry` return value to track visible row counts, frame budget overruns, etc. diff --git a/apps/website/content/docs/grid/density-helpers.mdx b/apps/website/content/docs/grid/density-helpers.mdx index c12804e29..c48cac663 100644 --- a/apps/website/content/docs/grid/density-helpers.mdx +++ b/apps/website/content/docs/grid/density-helpers.mdx @@ -49,7 +49,7 @@ The hook is SSR-safe. On the server (where `document` is undefined), the snapsho ### Used internally -`` and `` (private) both use `useResolvedHeights` to compute the body viewport height (`viewportHeight - headerHeight`) and to size the sticky header. When you render with `usePretableModel`, you typically call `useResolvedHeights()` yourself to compute the same — see the example in [Custom rendering](/docs/grid/custom-rendering). +`` and `` (private) both use `useResolvedHeights` to compute the body viewport height (`viewportHeight - headerHeight`) and to size the sticky header. When you render with `usePretable`, you typically call `useResolvedHeights()` yourself to compute the same — see the example in [Custom rendering](/docs/grid/custom-rendering). ## `getDensityHeights` — vanilla JS snapshot @@ -76,10 +76,10 @@ Use this when: | React component that should re-render on density/theme change | `useResolvedHeights` from `@pretable/react` | | Vanilla JS / non-React utility | `getDensityHeights` from `@pretable/ui` | | One-shot read at component mount (no reactivity needed) | Either; `getDensityHeights` is slightly leaner | -| Custom rendering with `usePretableModel` | `useResolvedHeights` (matches the engine's reads) | +| Custom rendering with `usePretable` | `useResolvedHeights` (matches the engine's reads) | ## Where to go next - [Theming > Density switching](/docs/theming/density) — recipe for wiring `data-density` from React state. -- [Custom rendering](/docs/grid/custom-rendering) — using `useResolvedHeights` with `usePretableModel`. +- [Custom rendering](/docs/grid/custom-rendering) — using `useResolvedHeights` with `usePretable`. - [API reference](/docs/grid/api-reference) — full type signatures. diff --git a/apps/website/content/docs/grid/index.mdx b/apps/website/content/docs/grid/index.mdx index 2c1685452..107ee6d86 100644 --- a/apps/website/content/docs/grid/index.mdx +++ b/apps/website/content/docs/grid/index.mdx @@ -29,13 +29,13 @@ What `` does NOT support out of the box: If you need any of the above, jump to Path 2. -### Path 2: `usePretable` / `usePretableModel` hooks (custom rendering) +### Path 2: `usePretable` hook (custom rendering) ```tsx -import { usePretableModel } from "@pretable/react"; +import { usePretable } from "@pretable/react"; function MyGrid({ rows, columns }) { - const { grid, snapshot, renderSnapshot, telemetry } = usePretableModel({ + const { grid, snapshot, renderSnapshot, telemetry } = usePretable({ columns, rows, viewportHeight: 480, @@ -87,9 +87,9 @@ See [Density helpers](/docs/grid/density-helpers) for the hook API and SSR consi The engine has more capabilities than this section covers: -- **Column autosize** — the `autosize` option on `usePretable` and `usePretableModel`; resizes columns to content. Not yet documented as a recipe. +- **Column autosize** — the `autosize` option on `usePretable`; resizes columns to content. Not yet documented as a recipe. - **Streaming and transactions** — `grid.applyTransaction({add, update, remove})` for live row updates. Streaming Overview lands in a subsequent docs PR. -- **Per-row measured heights** — pass `measuredHeights: Record` to `usePretableModel` for content-aware row sizing. The bench's `pretable-adapter.tsx` shows this pattern. +- **Per-row measured heights** — pass `measuredHeights: Record` to `usePretable` for content-aware row sizing. The bench's `pretable-adapter.tsx` shows this pattern. These ship as part of `@pretable/react` today; full doc coverage lands in subsequent releases. @@ -103,7 +103,7 @@ The engine ships full cell-range selection (Excel/Sheets semantics with shift-ex - [Selection](/docs/grid/selection) — cell-range model and three-state checkbox column. - [Keyboard](/docs/grid/keyboard) — full keyboard contract. - [Clipboard](/docs/grid/clipboard) — Cmd/Ctrl+C TSV defaults and overrides. -- [Custom rendering](/docs/grid/custom-rendering) — `usePretableModel` walkthrough with code. +- [Custom rendering](/docs/grid/custom-rendering) — `usePretable` walkthrough with code. - [Density helpers](/docs/grid/density-helpers) — `useResolvedHeights` and `getDensityHeights`. - [API reference](/docs/grid/api-reference) — model methods + types. - [Theming Overview](/docs/theming) — how the data-attribute contract gets styled. diff --git a/apps/website/content/docs/grid/keyboard.mdx b/apps/website/content/docs/grid/keyboard.mdx index 029eaafb0..650c46eaa 100644 --- a/apps/website/content/docs/grid/keyboard.mdx +++ b/apps/website/content/docs/grid/keyboard.mdx @@ -46,7 +46,7 @@ Both behaviors preserve the single-tab-stop model — once Tab leaves, the grid When the focused address changes (via keyboard, click, or programmatic `grid.setFocus`), a `useLayoutEffect` calls `.focus()` on the new cell's DOM node, so the browser's focus ring follows the engine state without flicker. Scrolling adjusts to keep the focused cell on-screen if needed. -This means consumers don't have to manage `tabIndex` themselves when using ``. If you're building with `usePretableModel` and rendering your own JSX, mirror the pattern: +This means consumers don't have to manage `tabIndex` themselves when using ``. If you're building with `usePretable` and rendering your own JSX, mirror the pattern: ```tsx const isFocused = diff --git a/apps/website/content/docs/grid/pretable-component.mdx b/apps/website/content/docs/grid/pretable-component.mdx index afa0163c6..52f1b5be0 100644 --- a/apps/website/content/docs/grid/pretable-component.mdx +++ b/apps/website/content/docs/grid/pretable-component.mdx @@ -74,13 +74,13 @@ The grid renders with Excel's surface tones, gridlines, accent. Toggle `data-den - **Focus / keyboard navigation** — wire `grid.setFocus({ rowId, columnId })` and `grid.moveFocus("up" | "down" | "left" | "right", options?)` to keyboard handlers. - **Custom cell components** — replace the default label/value layout. Custom rendering lets you render any React tree per cell. - **Pinned columns rendered as sticky** — set `column.pinned: "left"` and render with the data-pinned attribute. -- **Per-row measured heights** — provide `measuredHeights: Record` to `usePretableModel`. +- **Per-row measured heights** — provide `measuredHeights: Record` to `usePretable`. -All of the above are available with `usePretable` or `usePretableModel`. The drop-in component just doesn't expose the API surface for it. +All of the above are available with `usePretable`. The drop-in component just doesn't expose the API surface for it. ## Where to go next -- [Custom rendering](/docs/grid/custom-rendering) — `usePretableModel` walkthrough with code. +- [Custom rendering](/docs/grid/custom-rendering) — `usePretable` walkthrough with code. - [Density helpers](/docs/grid/density-helpers) — `useResolvedHeights` and `getDensityHeights`. - [API reference](/docs/grid/api-reference) — `PretableColumn`, `PretableGrid`, etc. - [Theming Overview](/docs/theming) — how to skin the grid. diff --git a/apps/website/content/docs/grid/pretable-surface.mdx b/apps/website/content/docs/grid/pretable-surface.mdx index d2970084b..505793bf3 100644 --- a/apps/website/content/docs/grid/pretable-surface.mdx +++ b/apps/website/content/docs/grid/pretable-surface.mdx @@ -40,11 +40,11 @@ export function EventStream({ events }: { events: Event[] }) { ## When to use this vs `` -| You want… | Reach for | -| -------------------------------------------------------------------------------------- | ------------------------------------------------- | -| A grid in 3 props, sensible defaults | [``](/docs/grid/pretable-component) | -| Custom cell renderers, telemetry, controlled interaction state, or fine-grained sizing | `` | -| To drive everything yourself with hooks | [`usePretableModel`](/docs/grid/custom-rendering) | +| You want… | Reach for | +| -------------------------------------------------------------------------------------- | --------------------------------------------- | +| A grid in 3 props, sensible defaults | [``](/docs/grid/pretable-component) | +| Custom cell renderers, telemetry, controlled interaction state, or fine-grained sizing | `` | +| To drive everything yourself with hooks | [`usePretable`](/docs/grid/custom-rendering) | `` is a 115-line wrapper around `` with hardcoded `viewportHeight={320}`, hardcoded cell renderers, and no telemetry. If any of that doesn't fit, drop down to the surface. @@ -197,6 +197,6 @@ If your use case looks like log inspection, start with ``. Other - [Selection](/docs/grid/selection) — cell-range model and checkbox column. - [Keyboard](/docs/grid/keyboard) — the full keyboard contract. - [Clipboard](/docs/grid/clipboard) — Cmd/Ctrl+C TSV defaults and overrides. -- [Custom rendering](/docs/grid/custom-rendering) — `usePretableModel` walkthrough for headless control. +- [Custom rendering](/docs/grid/custom-rendering) — `usePretable` walkthrough for headless control. - [Density helpers](/docs/grid/density-helpers) — `useResolvedHeights` for density-aware row sizing. - [API reference](/docs/grid/api-reference) — full type signatures. diff --git a/apps/website/content/docs/grid/selection.mdx b/apps/website/content/docs/grid/selection.mdx index 2e615df37..fa5292fc7 100644 --- a/apps/website/content/docs/grid/selection.mdx +++ b/apps/website/content/docs/grid/selection.mdx @@ -5,7 +5,7 @@ nav: Grid order: 4 --- -Pretable's selection model is **cell-range first** — Excel/Sheets semantics. Row selection is derived from the cell-range state, exposed via a built-in three-state checkbox column. The same engine state powers ``, ``, and the `usePretableModel` hook. +Pretable's selection model is **cell-range first** — Excel/Sheets semantics. Row selection is derived from the cell-range state, exposed via a built-in three-state checkbox column. The same engine state powers ``, ``, and the `usePretable` hook. ## Selection model diff --git a/docs/superpowers/plans/2026-05-08-tier1-api-stabilization-pr3-react.md b/docs/superpowers/plans/2026-05-08-tier1-api-stabilization-pr3-react.md new file mode 100644 index 000000000..eaafcbe90 --- /dev/null +++ b/docs/superpowers/plans/2026-05-08-tier1-api-stabilization-pr3-react.md @@ -0,0 +1,1010 @@ +# Tier 1 Sub-project A — PR 3 (`@pretable/react` audit) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Audit `@pretable/react`'s public surface — rename `usePretableModel` → `usePretable` (deleting the old low-level `usePretable`), demote three internal-leakage exports to `ɵ`-prefix, tag two experimental components `@beta`, fix one forgotten-export leak, retire one alias, write `public_api.ts` + TSDoc + per-package README. + +**Architecture:** Mechanical rename ripple through hook source + one component file + 2 react tests + 8 website docs. Then add TSDoc + release tags on declarations across ~10 source files. Move public re-exports to a curated `public_api.ts`; collapse `index.ts` to one line. Regenerate `react.api.md` and audit the diff. + +**Tech Stack:** TypeScript, React 18+, `@microsoft/api-extractor`, `@microsoft/tsdoc`, pnpm workspaces, vitest, fumadocs (the website's MDX). + +**Source spec:** `docs/superpowers/specs/2026-05-08-tier1-api-stabilization-pr3-react-design.md` + +--- + +## File Structure + +| Path | Responsibility | Action | +| -------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `packages/react/src/use-pretable.ts` | Hook(s) | Rewrite (delete old `usePretable`, rename `usePretableModel` → `usePretable`, rename options type) | +| `packages/react/src/pretable-surface.tsx` | Surface component | Modify (`usePretableModel` import + call site) + add TSDoc on `PretableSurface`, `PretableSurfaceProps`, `PretableSurfaceMessages`, `RowSelectionColumnConfig` | +| `packages/react/src/pretable.tsx` | Drop-in component | Modify (TSDoc on `Pretable`, `PretableProps`) | +| `packages/react/src/inspection-grid.tsx` | Inspection surface | Modify (`@beta` TSDoc on `InspectionGrid` + `InspectionGridProps`) | +| `packages/react/src/labeled-grid-surface.tsx` | Labeled surface | Modify (`@beta` TSDoc on `LabeledGridSurface`, `LabeledGridSurfaceProps`, `LabeledGridSurfaceFormatValueInput`); replace local `PretableSurfaceSortDirection` alias with `PretableSortDirection` import from core | +| `packages/react/src/types.ts` | React-extended column types | Modify (TSDoc on `PretableColumn`, `PretableCellRenderInput`, `PretableHeaderRenderInput`, re-export `PretableFormatInput` already there) | +| `packages/react/src/density.ts` | Density hook | Modify (`@internal` on `useResolvedHeights`; TSDoc on `DensityHeights`) | +| `packages/react/src/row-height.ts` | Measurement helper | Modify (`@internal` on `measureRenderedRowHeight`) | +| `packages/react/src/copy.ts` | Clipboard helpers | Modify (TSDoc on `defaultCoerceForCopy`, `serializeRangesAsTsv`, `CopyPayload`, `SerializeRangesArgs`) | +| `packages/react/src/constants.ts` | Constants | Modify (`@internal` on `ROW_SELECT_COLUMN_ID`) | +| `packages/react/src/public_api.ts` | **NEW** curated public re-exports | Create | +| `packages/react/src/index.ts` | Package entry | Collapse to `export * from './public_api';` | +| `packages/react/src/__tests__/pretable-surface.test.tsx` | Tests | Modify (rename `usePretableModel` → `usePretable`) | +| `packages/react/src/__tests__/pretable.test.tsx` | Tests | Modify (rename `usePretableModel` → `usePretable`) | +| `apps/website/content/docs/grid/*.mdx` (8 files) | Docs | Modify (rename hook + options type references) | +| `packages/react/react.api.md` | Generated baseline | Regenerate | +| `packages/react/README.md` | **NEW** per-package README | Create | + +--- + +## Task 1: Hook rename — delete old `usePretable`, rename `usePretableModel` → `usePretable` + +**Files:** + +- Modify: `packages/react/src/use-pretable.ts` +- Modify: `packages/react/src/pretable-surface.tsx` +- Modify: `packages/react/src/__tests__/pretable-surface.test.tsx` +- Modify: `packages/react/src/__tests__/pretable.test.tsx` +- Modify: 8 website doc files under `apps/website/content/docs/grid/` + +- [ ] **Step 1: Rewrite `packages/react/src/use-pretable.ts`** + +The hook source has both old `usePretable` (returns `PretableGrid`) and `usePretableModel` (returns `PretableModel`). Delete the old one, rename the rich one, rename the options type. The body of the new `usePretable` is exactly today's `usePretableModel` body but with the inner `usePretable({...})` call replaced by an inlined `useMemo(() => createGrid(...))`. + +Replace ALL contents with: + +````ts +import { + type AutosizeOptions, + createGrid, + type PretableFocusState, + type PretableGrid, + type PretableGridOptions, + type PretableGridSnapshot, + type PretableRow, + type PretableSelectionState, + type PretableSortState, +} from "@pretable/core"; +import type { PretableColumn } from "./types"; +import { + createDomRenderSnapshot, + type PlannedColumn, +} from "@pretable-internal/renderer-dom"; +import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react"; + +/** + * One row of layout-derived render state for use during custom rendering. + * + * @public + */ +export interface PretableRenderRow { + id: string; + row: TRow; + rowIndex: number; + top: number; + height: number; +} + +/** + * Layout-derived render snapshot returned by {@link usePretable}. Drives + * positioned-cell rendering — every column has a left + width, every visible + * row has a top + height. + * + * @public + */ +export interface PretableRenderSnapshot< + TRow extends PretableRow = PretableRow, +> { + columns: PlannedColumn[]; + rows: PretableRenderRow[]; + nodeCount: number; + totalHeight: number; + totalWidth: number; +} + +/** + * Telemetry numbers about the current render — counts and ranges suitable + * for status bars, dev panels, or virtualization debugging. + * + * @public + */ +export interface PretableTelemetry { + focusedRowId: string | null; + rowModelRowCount: number; + renderedRowCount: number; + selectedRowId: string | null; + totalRowCount: number; + totalHeight: number; + visibleRowCount: number; + visibleRowRange: { + end: number; + start: number; + }; +} + +/** + * **Input** shape for controlling a {@link PretableSurface} from the outside. + * Pass the slices you want to control; omit slices you want the grid to own. + * + * @public + */ +export interface PretableSurfaceState { + filters?: Record; + focus?: PretableFocusState; + selection?: PretableSelectionState; + sort?: PretableSortState | null; + columnWidths?: Record; + columnOrder?: readonly string[]; + columnPinned?: Record; +} + +/** + * Options for the {@link usePretable} hook. + * + * @public + */ +export interface UsePretableOptions { + autosize?: boolean | AutosizeOptions; + columns: PretableColumn[]; + rows: TRow[]; + getRowId?: PretableGridOptions["getRowId"]; + viewportHeight: number; + viewportWidth?: number; + overscan?: number; + state?: PretableSurfaceState | null; + measuredHeights?: Record; + onSelectionChange?: (next: PretableSelectionState) => void; + onFocusChange?: (next: PretableFocusState) => void; +} + +/** + * Output of the {@link usePretable} hook — a stable handle plus the latest + * snapshot, render layout, and telemetry. + * + * @public + */ +export interface PretableModel { + grid: PretableGrid; + snapshot: PretableGridSnapshot; + renderSnapshot: PretableRenderSnapshot; + telemetry: PretableTelemetry; +} + +/** + * The primary React hook. Creates a grid, applies optional controlled state, + * and returns the latest snapshot, layout-derived render snapshot, and + * telemetry. Suitable for custom rendering — `` itself is + * built on top of this hook. + * + * @example + * ```tsx + * const { grid, snapshot, renderSnapshot, telemetry } = usePretable({ + * columns, + * rows, + * viewportHeight: 480, + * }); + * ``` + * + * @public + */ +export function usePretable({ + autosize, + columns, + rows, + getRowId, + viewportHeight, + viewportWidth, + overscan = 6, + state, + measuredHeights, + onSelectionChange, + onFocusChange, +}: UsePretableOptions): PretableModel { + const grid = useMemo( + () => createGrid({ columns, rows, getRowId, autosize }), + [autosize, columns, getRowId, rows], + ); + + const lastColumnIdsRef = useRef(null); + useLayoutEffect(() => { + const currentIds = columns.map((c) => c.id); + const prevIds = lastColumnIdsRef.current; + if ( + prevIds === null || + prevIds.length !== currentIds.length || + prevIds.some((id, i) => id !== currentIds[i]) + ) { + if (prevIds !== null) { + grid.mergeColumnsFromProps(columns); + } + lastColumnIdsRef.current = currentIds; + } + }, [columns, grid]); + + // onSelectionChange / onFocusChange callbacks are wired in the surface's + // event handlers (keyboard, click) directly. This keeps callbacks firing + // for user-induced changes even when the corresponding slice is controlled + // — diff-detection here would race the controlled-prop reapply below. + void onSelectionChange; + void onFocusChange; + + if (state) { + if (state.sort !== undefined) { + grid.setSort(state.sort?.columnId ?? null, state.sort?.direction ?? null); + } + + if (state.filters !== undefined) { + grid.replaceFilters(state.filters); + } + + if (state.columnWidths !== undefined) { + const widths = state.columnWidths; + for (const column of grid.options.columns) { + const next = widths[column.id]; + if (next !== undefined && next !== column.widthPx) { + grid.setColumnWidth(column.id, next); + } + } + } + + if (state.columnOrder !== undefined) { + const targetOrder = state.columnOrder; + const currentIds = grid.options.columns.map((c) => c.id); + const targetIds = [ + ...targetOrder.filter((id) => currentIds.includes(id)), + ...currentIds.filter((id) => !targetOrder.includes(id)), + ]; + for (let i = 0; i < targetIds.length; i += 1) { + const id = targetIds[i]!; + const currentIdx = grid.options.columns.findIndex((c) => c.id === id); + if (currentIdx !== i && id !== "__pretable_row_select__") { + grid.moveColumn(id, i); + } + } + } + + if (state.columnPinned !== undefined) { + const pinned = state.columnPinned; + for (const [id, value] of Object.entries(pinned)) { + const column = grid.options.columns.find((c) => c.id === id); + if (!column) continue; + const targetPinned = value === "left" ? "left" : null; + const currentPinned = column.pinned ?? null; + if (currentPinned !== targetPinned) { + grid.setColumnPinned(id, targetPinned); + } + } + } + + if (state.selection !== undefined) { + grid.setSelection(state.selection); + } + + if (state.focus !== undefined) { + const focus = state.focus; + + if (focus.rowId !== null && focus.columnId !== null) { + grid.setFocus({ rowId: focus.rowId, columnId: focus.columnId }); + } else { + grid.setFocus(null); + } + } + } + + const snapshot = useSyncExternalStore( + grid.subscribe, + grid.getSnapshot, + grid.getSnapshot, + ); + + useLayoutEffect(() => { + if ( + snapshot.viewport.height === viewportHeight && + snapshot.viewport.width === (viewportWidth ?? 0) + ) { + return; + } + + grid.setViewport({ + scrollTop: snapshot.viewport.scrollTop, + scrollLeft: snapshot.viewport.scrollLeft, + height: viewportHeight, + width: viewportWidth ?? 0, + }); + }, [ + grid, + snapshot.viewport.height, + snapshot.viewport.width, + snapshot.viewport.scrollTop, + snapshot.viewport.scrollLeft, + viewportHeight, + viewportWidth, + ]); + + const renderSnapshot = useMemo>( + () => + createDomRenderSnapshot({ + columns: grid.options.columns, + snapshot, + scrollTop: snapshot.viewport.scrollTop, + scrollLeft: snapshot.viewport.scrollLeft, + viewportHeight, + viewportWidth, + overscan, + measuredHeights, + }), + [ + grid.options.columns, + measuredHeights, + overscan, + snapshot, + viewportHeight, + viewportWidth, + ], + ); + const telemetry = useMemo(() => { + const viewportBottom = + snapshot.viewport.scrollTop + + Math.max(snapshot.viewport.height, viewportHeight); + const viewportRows = renderSnapshot.rows.filter((row) => { + const rowBottom = row.top + row.height; + + return ( + row.top < viewportBottom && rowBottom > snapshot.viewport.scrollTop + ); + }); + const firstVisibleRow = viewportRows[0]; + const lastVisibleRow = viewportRows[viewportRows.length - 1]; + + return { + focusedRowId: snapshot.focus.rowId, + rowModelRowCount: snapshot.visibleRows.length, + renderedRowCount: renderSnapshot.rows.length, + selectedRowId: snapshot.selection.ranges[0]?.startRowId ?? null, + totalRowCount: snapshot.totalRowCount, + totalHeight: renderSnapshot.totalHeight, + visibleRowCount: viewportRows.length, + visibleRowRange: + firstVisibleRow && lastVisibleRow + ? { + start: firstVisibleRow.rowIndex, + end: lastVisibleRow.rowIndex + 1, + } + : { + start: 0, + end: 0, + }, + }; + }, [ + renderSnapshot.rows, + renderSnapshot.totalHeight, + snapshot.focus.rowId, + snapshot.visibleRows.length, + snapshot.selection.ranges, + snapshot.totalRowCount, + snapshot.viewport.height, + snapshot.viewport.scrollTop, + viewportHeight, + ]); + + return { + grid, + snapshot, + renderSnapshot, + telemetry, + }; +} +```` + +- [ ] **Step 2: Update `packages/react/src/pretable-surface.tsx`** + +Find the import (around line 38): + +```ts + usePretableModel, +``` + +Rename to `usePretable`. + +Find the call site (around line 510): + +```ts + const { grid, snapshot, renderSnapshot, telemetry } = usePretableModel({ +``` + +Rename to `usePretable`. + +- [ ] **Step 3: Update test files** + +In `packages/react/src/__tests__/pretable-surface.test.tsx`, change every occurrence of `usePretableModel` to `usePretable`. Specifically: + +- The import on line 21: `import { type PretableSurfaceState, usePretableModel } from "../use-pretable";` → `import { type PretableSurfaceState, usePretable } from "../use-pretable";` +- The describe-block label on line 84 (`"exposes renderer telemetry from usePretableModel..."`) → `"exposes renderer telemetry from usePretable..."` +- The call on line 86: `const model = usePretableModel({` → `const model = usePretable({` + +In `packages/react/src/__tests__/pretable.test.tsx`, change every occurrence of `usePretableModel` to `usePretable`. The import on line 6 and two call sites at lines 293, 346. + +- [ ] **Step 4: Update website docs** + +Apply this rename across the 8 doc files. From the worktree root: + +```bash +files=( + apps/website/content/docs/grid/keyboard.mdx + apps/website/content/docs/grid/selection.mdx + apps/website/content/docs/grid/density-helpers.mdx + apps/website/content/docs/grid/pretable-surface.mdx + apps/website/content/docs/grid/custom-rendering.mdx + apps/website/content/docs/grid/index.mdx + apps/website/content/docs/grid/pretable-component.mdx + apps/website/content/docs/grid/api-reference.mdx +) +for f in "${files[@]}"; do + sed -i '' \ + -e 's/UsePretableModelOptions/UsePretableOptions/g' \ + -e 's/usePretableModel/usePretable/g' \ + "$f" +done +``` + +Then specifically `apps/website/content/docs/grid/index.mdx` line 32 has a Markdown heading that mentions both names: `### Path 2: \`usePretable\` / \`usePretableModel\` hooks (custom rendering)`. The sed run above turns it into `### Path 2: \`usePretable\` / \`usePretable\` hooks (custom rendering)`— duplicate. Open the file and reduce that heading to`### Path 2: \`usePretable\` hook (custom rendering)`. + +Similarly line 90 reads "the `autosize` option on `usePretable` and `usePretableModel`" — after sed it becomes "the `autosize` option on `usePretable` and `usePretable`". Open and reduce to "the `autosize` option on `usePretable`". + +Search for any other `usePretable.*usePretable` duplications in those 8 files with `grep -n "usePretable.*usePretable" apps/website/content/docs/grid/*.mdx` and clean each one — usually deleting the second mention. + +- [ ] **Step 5: Verify typecheck + tests** + +```bash +pnpm --filter @pretable/react typecheck && \ +pnpm --filter @pretable/react test && \ +pnpm --filter @pretable/app-website typecheck +``` + +Expected: all pass. + +- [ ] **Step 6: Commit** + +```bash +git add packages/react/src/use-pretable.ts \ + packages/react/src/pretable-surface.tsx \ + packages/react/src/__tests__/pretable-surface.test.tsx \ + packages/react/src/__tests__/pretable.test.tsx \ + apps/website/content/docs/grid +git commit -m "refactor(react): rename usePretableModel → usePretable; delete low-level usePretable + +The simple-sounding name now points to the hook docs already use. +Old usePretable (returning just PretableGrid) was a one-line useMemo +wrapper around createGrid; deleted. UsePretableModelOptions is now +UsePretableOptions; the strict-subset old UsePretableOptions deleted. + +Co-Authored-By: Claude Opus 4.7 " +``` + +--- + +## Task 2: Fix `PretableSurfaceSortDirection` forgotten-export leak + +**Files:** + +- Modify: `packages/react/src/labeled-grid-surface.tsx` + +- [ ] **Step 1: Read the file's imports** + +`packages/react/src/labeled-grid-surface.tsx` line 1 region currently imports from `@pretable/core`. Add `PretableSortDirection` to that import: + +```ts +import type { + PretableGridOptions, + PretableRow, + PretableSortDirection, +} from "@pretable/core"; +``` + +(Adjust based on what's currently imported — preserve existing imports, add `PretableSortDirection` to the type-import list. If `@pretable/core` isn't already imported as `import type`, add a new line.) + +- [ ] **Step 2: Replace the `sortDirection` field type** + +Find line 28 (in interface `LabeledGridSurfaceFormatValueInput`): + +```ts +sortDirection: PretableSurfaceSortDirection; +``` + +Change to: + +```ts +sortDirection: PretableSortDirection; +``` + +- [ ] **Step 3: Delete the local alias** + +Find lines 217 region: + +```ts +type PretableSurfaceSortDirection = NonNullable< + Parameters< + NonNullable + >[0]["sortDirection"] +> | null; +``` + +Delete this declaration entirely. + +- [ ] **Step 4: Verify typecheck** + +```bash +pnpm --filter @pretable/react typecheck +``` + +Expected: passes. + +- [ ] **Step 5: Commit** + +```bash +git add packages/react/src/labeled-grid-surface.tsx +git commit -m "refactor(react): replace local PretableSurfaceSortDirection with PretableSortDirection from core + +Eliminates the ae-forgotten-export warning on react.api.md. The local +alias was just a more verbose way of saying 'asc | desc | null'. + +Co-Authored-By: Claude Opus 4.7 " +``` + +--- + +## Task 3: Add TSDoc + release tags on declarations + +**Files:** + +- Modify: `packages/react/src/pretable.tsx` +- Modify: `packages/react/src/pretable-surface.tsx` +- Modify: `packages/react/src/inspection-grid.tsx` +- Modify: `packages/react/src/labeled-grid-surface.tsx` +- Modify: `packages/react/src/types.ts` +- Modify: `packages/react/src/density.ts` +- Modify: `packages/react/src/row-height.ts` +- Modify: `packages/react/src/copy.ts` +- Modify: `packages/react/src/constants.ts` + +For each declaration listed below, prepend the indicated TSDoc comment block immediately above the declaration. Block format: + +```ts +/** + * + * + * @public // or @beta or @internal + */ +export ... +``` + +### `packages/react/src/pretable.tsx` + +| Declaration | Summary | Tag | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `export interface PretableProps<…>` | "Props for the {@link Pretable} drop-in component." | `@public` | +| `export function Pretable<…>` | "Drop-in pretable component. Wraps {@link PretableSurface} with internal state — pass `columns` and `rows` and you're done. Reach for `PretableSurface` when you need to control state from the outside." | `@public` | + +### `packages/react/src/pretable-surface.tsx` + +| Declaration | Summary | Tag | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `export interface PretableSurfaceProps<…>` | "Props for {@link PretableSurface}." | `@public` | +| `export interface PretableSurfaceMessages` | "Localizable user-facing strings rendered by {@link PretableSurface}. Pass to override the English defaults." | `@public` | +| `export interface RowSelectionColumnConfig` | "Configuration for the synthetic row-select column rendered by {@link PretableSurface} when `rowSelectionColumn` is enabled." | `@public` | +| `export const ROW_SELECT_COLUMN_ID` | "Reserved column id for the synthetic row-select checkbox column. Internal use; surface authors shouldn't reference this directly." | `@internal` | +| `export function PretableSurface<…>` | "Controlled grid surface. The primary React component. Pass `state` to control any subset of sort/filter/selection/focus/column-layout from the outside; omit slices you want the grid to own." | `@public` | + +### `packages/react/src/inspection-grid.tsx` + +| Declaration | Summary | Tag | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------- | +| `export interface InspectionGridProps` | "Props for {@link InspectionGrid}." | `@beta` | +| `export function InspectionGrid` | "Special-purpose inspection surface that renders rows as labeled key/value pairs. Experimental — shape may change before 1.0." | `@beta` | + +### `packages/react/src/labeled-grid-surface.tsx` + +| Declaration | Summary | Tag | +| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------- | +| `export interface LabeledGridSurfaceFormatValueInput<…>` | "Input passed to a {@link LabeledGridSurface} format function." | `@beta` | +| `export interface LabeledGridSurfaceProps<…>` | "Props for {@link LabeledGridSurface}." | `@beta` | +| `export function LabeledGridSurface<…>` | "Special-purpose surface for label/value-style table layouts. Experimental — shape may change before 1.0." | `@beta` | + +### `packages/react/src/types.ts` + +| Declaration | Summary | Tag | +| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `export interface PretableColumn<…>` | "React-extended column definition. Adds the `render` and `renderHeader` JSX-typed callbacks on top of `@pretable/core`'s base column." | `@public` | +| `export interface PretableCellRenderInput<…>` | "Input passed to a column's `render` function." | `@public` | +| `export interface PretableHeaderRenderInput<…>` | "Input passed to a column's `renderHeader` function." | `@public` | + +The existing `export type { PretableFormatInput };` at the bottom is a re-export from core — its `@public` tag lives in core. + +### `packages/react/src/density.ts` + +| Declaration | Summary | Tag | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `export interface DensityHeights` | "CSS-token-derived heights used by `` / `` to size header and rows. PR 4 may consolidate the source of truth between this package and `@pretable/ui`." | `@public` | +| `export function useResolvedHeights(…)` | "React hook returning the current density heights derived from the active CSS theme. Internal — `` and `` use this; external consumers should reach for `getDensityHeights` from `@pretable/ui` when PR 4 lands." | `@internal` | + +### `packages/react/src/row-height.ts` + +| Declaration | Summary | Tag | +| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------- | +| `export function measureRenderedRowHeight(…)` | "DOM measurement helper used internally by the surface's row-height accounting. Not part of the user-facing API." | `@internal` | + +### `packages/react/src/copy.ts` + +| Declaration | Summary | Tag | +| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------- | +| `export interface CopyPayload` | "Plain-text + HTML pair returned by clipboard serializers and consumed by `onCopy` / `copyToClipboard` props." | `@public` | +| `export interface SerializeRangesArgs<…>` | "Input for {@link serializeRangesAsTsv}." | `@public` | +| `export function defaultCoerceForCopy(…)` | "Default coerce-value-to-string used during clipboard serialization. Useful as a fallback inside custom serializers." | `@public` | +| `export function serializeRangesAsTsv(…)` | "Serialize one or more `PretableCellRange`s to a tab-separated text + HTML payload suitable for clipboard write." | `@public` | + +### `packages/react/src/constants.ts` + +The file is one line: + +```ts +export const ROW_SELECT_COLUMN_ID = "__pretable_row_select__"; +``` + +Add the `@internal` block above the export: + +```ts +/** + * Reserved column id for the synthetic row-select checkbox column. + * Internal — surface authors shouldn't reference this directly. + * + * @internal + */ +export const ROW_SELECT_COLUMN_ID = "__pretable_row_select__"; +``` + +Note: `pretable-surface.tsx` re-exports `ROW_SELECT_COLUMN_ID`. The TSDoc above attaches to the `constants.ts` declaration, which is what api-extractor reads. + +- [ ] **Step 1: Apply the TSDoc + tag for every declaration above** + +Do them one file at a time, working through the table. Each comment block must be immediately above the `export` keyword (no blank line between). + +- [ ] **Step 2: Verify typecheck** + +```bash +pnpm --filter @pretable/react typecheck +``` + +Expected: passes. (TSDoc is comments — typecheck doesn't validate, but it must not break.) + +- [ ] **Step 3: Commit** + +```bash +git add packages/react/src +git commit -m "feat(react): TSDoc + @public/@beta/@internal tags on declarations + +Adds TSDoc summaries with release tags on every public, beta, and +internal symbol so api-extractor's report shows annotated API +instead of @public (undocumented). Tags InspectionGrid, +LabeledGridSurface, and their props @beta. Tags useResolvedHeights, +measureRenderedRowHeight, ROW_SELECT_COLUMN_ID @internal — public_api.ts +will re-export them with ɵ-prefix in the next task. + +Co-Authored-By: Claude Opus 4.7 " +``` + +--- + +## Task 4: Write `public_api.ts`; collapse `index.ts`; remove `PretableCoreColumn` + +**Files:** + +- Create: `packages/react/src/public_api.ts` +- Modify: `packages/react/src/index.ts` + +- [ ] **Step 1: Create `packages/react/src/public_api.ts`** + +```ts +/** + * Public API of `@pretable/react`. Hand-curated re-exports — do not edit + * `index.ts` directly. Internal symbols stay in their source files and are + * re-exported here under the `ɵ`-prefix when other `@pretable/*` packages + * (or future internal hooks) need them. + * + * @packageDocumentation + */ + +// Components +export { Pretable } from "./pretable"; +export { PretableSurface } from "./pretable-surface"; +export { InspectionGrid } from "./inspection-grid"; +export { LabeledGridSurface } from "./labeled-grid-surface"; + +// Hooks +export { usePretable } from "./use-pretable"; + +// Component prop / message / config types +export type { PretableProps } from "./pretable"; +export type { + PretableSurfaceMessages, + PretableSurfaceProps, + RowSelectionColumnConfig, +} from "./pretable-surface"; +export type { InspectionGridProps } from "./inspection-grid"; +export type { + LabeledGridSurfaceFormatValueInput, + LabeledGridSurfaceProps, +} from "./labeled-grid-surface"; + +// Hook input + output shapes +export type { + PretableModel, + PretableRenderRow, + PretableRenderSnapshot, + PretableSurfaceState, + PretableTelemetry, + UsePretableOptions, +} from "./use-pretable"; + +// React-extended column type + render-input shapes +export type { + PretableCellRenderInput, + PretableColumn, + PretableFormatInput, + PretableHeaderRenderInput, +} from "./types"; + +// Copy / clipboard +export { defaultCoerceForCopy, serializeRangesAsTsv } from "./copy"; +export type { CopyPayload, SerializeRangesArgs } from "./copy"; + +// Density +export type { DensityHeights } from "./density"; + +// Re-exports from @pretable/core (the engine types react users typically +// touch — full headless surface lives in @pretable/core) +export type { + PretableGrid, + PretableGridOptions, + PretableGridSnapshot, + PretableRow, +} from "@pretable/core"; + +// Internal-but-exported (ɵ-prefix marks these as not API-stable) +export { useResolvedHeights as ɵuseResolvedHeights } from "./density"; +export { measureRenderedRowHeight as ɵmeasureRenderedRowHeight } from "./row-height"; +export { ROW_SELECT_COLUMN_ID as ɵROW_SELECT_COLUMN_ID } from "./constants"; +``` + +Note: `PretableCoreColumn` is **not** re-exported (deleted per the spec). The unprefixed `useResolvedHeights`, `measureRenderedRowHeight`, `ROW_SELECT_COLUMN_ID` are also gone — only the `ɵ`-prefixed versions ship. + +- [ ] **Step 2: Replace `packages/react/src/index.ts` with one line** + +```ts +export * from "./public_api"; +``` + +- [ ] **Step 3: Verify typecheck — react and the website** + +```bash +pnpm --filter @pretable/react typecheck && \ +pnpm --filter @pretable/app-website typecheck && \ +pnpm --filter @pretable/app-bench typecheck +``` + +Expected: all pass. The website's docs reference public symbols only; `apps/bench` uses `PretableSurface` and `PretableColumn` (both still exported). + +- [ ] **Step 4: Commit** + +```bash +git add packages/react/src/public_api.ts packages/react/src/index.ts +git commit -m "feat(react): hand-curated public_api.ts; remove PretableCoreColumn + +public_api.ts is the single source of truth for the package's public +surface. index.ts collapses to a one-line re-export. Three internal-use +exports demoted to ɵ-prefix at the public boundary: +ɵuseResolvedHeights, ɵmeasureRenderedRowHeight, ɵROW_SELECT_COLUMN_ID. +PretableCoreColumn alias deleted — use PretableColumn from @pretable/core +if the headless base type is what you need. + +Co-Authored-By: Claude Opus 4.7 " +``` + +--- + +## Task 5: Regenerate `react.api.md` and audit the diff + +**Files:** + +- Modify: `packages/react/react.api.md` + +- [ ] **Step 1: Regenerate** + +```bash +pnpm --filter @pretable/react build && \ +pnpm --filter @pretable/react api +``` + +Expected: `API Extractor completed successfully`. The regenerated `packages/react/react.api.md` should differ substantially from current. + +- [ ] **Step 2: Audit the diff against the spec's success criteria** + +```bash +git diff packages/react/react.api.md | head -150 +``` + +Verify each of these checks holds: + +- `usePretableModel` and `UsePretableModelOptions` are gone (search the new file with `grep -n "usePretableModel\|UsePretableModelOptions" packages/react/react.api.md` — expected: nothing). +- `usePretable` exists and returns `PretableModel` (not `PretableGrid`). +- `UsePretableOptions` exists with the merged shape: `autosize`, `columns`, `rows`, `getRowId`, `viewportHeight`, `viewportWidth`, `overscan`, `state`, `measuredHeights`, `onSelectionChange`, `onFocusChange`. +- `useResolvedHeights`, `measureRenderedRowHeight`, `ROW_SELECT_COLUMN_ID` are gone; `ɵuseResolvedHeights`, `ɵmeasureRenderedRowHeight`, `ɵROW_SELECT_COLUMN_ID` are present. +- `PretableCoreColumn` is gone. +- `PretableSurfaceSortDirection` is gone (and `LabeledGridSurfaceFormatValueInput.sortDirection: PretableSortDirection` instead). +- `// @beta` annotations on `InspectionGrid`, `InspectionGridProps`, `LabeledGridSurface`, `LabeledGridSurfaceProps`, `LabeledGridSurfaceFormatValueInput`. +- Most public symbols show `// @public` (not `// @public (undocumented)`). +- `ae-forgotten-export` warnings reduced from current count — ideally zero, but cross-package leaks from `@pretable-internal/grid-core` types may persist if api-extractor doesn't always inline through bundledPackages. Note any remaining warnings; acceptable if they're pre-existing. + +If any of the named symbols don't behave as expected, STOP — there's a bug. + +- [ ] **Step 3: Verify `api:check` passes for all 4 packages** + +```bash +pnpm api:check +``` + +Expected: all 4 `API Extractor completed successfully`, exit 0. + +- [ ] **Step 4: Commit** + +```bash +git add packages/react/react.api.md +git commit -m "chore(api): regenerate react.api.md after audit + +Hook rename (usePretableModel → usePretable), 3 demotions to ɵ-prefix, +4 @beta tags on Inspection/Labeled surfaces, deletion of +PretableCoreColumn alias and PretableSurfaceSortDirection leak, +@public TSDoc on every other public symbol. + +Co-Authored-By: Claude Opus 4.7 " +``` + +--- + +## Task 6: Write `packages/react/README.md` + +**Files:** + +- Create: `packages/react/README.md` + +- [ ] **Step 1: Write the README** + +````markdown +# @pretable/react + +The React surface for [pretable](https://pretable.dev/). Ships three components and one hook on top of `@pretable/core`'s headless engine. + +## When to reach for what + +- **``** — drop-in. Pass `columns` and `rows` and you're done. Best for quick wins. +- **``** — controlled. Pass `state` to drive sort, filter, selection, focus, or column layout from the outside. Best for production apps. +- **`usePretable`** — the hook. Returns `{ grid, snapshot, renderSnapshot, telemetry }` for custom rendering. Best when you need more control than `` provides — e.g., a non-default DOM layout or a different framework. + +`` and `` are special-purpose surfaces tagged `@beta` — they work, but expect shape changes pre-1.0. + +## Install + +```sh +npm install @pretable/react @pretable/core @pretable/ui +# or pnpm add … / yarn add … +``` +```` + +`@pretable/ui` ships the CSS theme; import the stylesheet once at the root of your app. + +## Minimal example + +```tsx +import { Pretable } from "@pretable/react"; +import "@pretable/ui/grid.css"; + +function App() { + return ( + + ); +} +``` + +## Full public surface + +See **[`react.api.md`](./react.api.md)** for every exported component, hook, type, and function with their full signatures. The file is generated by [API Extractor](https://api-extractor.com/) and committed to the repo; CI fails if it drifts. + +## License + +MIT — see [LICENSE](../../LICENSE). + +```` + +- [ ] **Step 2: Commit** + +```bash +git add packages/react/README.md +git commit -m "docs(react): add per-package README + +Co-Authored-By: Claude Opus 4.7 " +```` + +--- + +## Task 7: Final repo-wide gates and PR + +**Files:** none (verification + push + PR creation) + +- [ ] **Step 1: Run all repo-wide gates** + +```bash +pnpm -w typecheck && pnpm -w test && pnpm -w lint && pnpm format && pnpm api:check +``` + +Expected: every command exits 0. If `pnpm format` complains about any markdown files (the website docs got modified by sed in Task 1), run `pnpm format:write` and amend the relevant commit (or add a small follow-up commit). + +- [ ] **Step 2: Push** + +```bash +git push -u origin api-stabilization-react +``` + +- [ ] **Step 3: Open the PR** + +```bash +gh pr create --title "refactor(react): audit @pretable/react public surface; rename usePretableModel → usePretable" --body "$(cat <<'EOF' +## Summary + +PR 3 of 5 for [Tier 1 Sub-project A — Public API Stabilization](docs/superpowers/specs/2026-05-07-tier1-public-api-stabilization-design.md). Audits \`@pretable/react\` per [PR 3's design spec](docs/superpowers/specs/2026-05-08-tier1-api-stabilization-pr3-react-design.md). + +- **Hook brevity rename.** \`usePretableModel\` → \`usePretable\` (the simple-sounding name now points to the hook docs already use). The old low-level \`usePretable\` (just \`useMemo(() => createGrid(opts))\`) is deleted; consumers can inline that pattern if needed. \`UsePretableModelOptions\` → \`UsePretableOptions\`; the strict-subset old \`UsePretableOptions\` is deleted. +- **\`public_api.ts\` convention.** Hand-curated curated public surface; \`index.ts\` is one line. +- **Three demotions to \`ɵ\`-prefix.** \`useResolvedHeights\` → \`ɵuseResolvedHeights\`, \`measureRenderedRowHeight\` → \`ɵmeasureRenderedRowHeight\`, \`ROW_SELECT_COLUMN_ID\` → \`ɵROW_SELECT_COLUMN_ID\`. Tagged \`@internal\` at source. +- **Two \`@beta\` tags.** \`InspectionGrid\` and \`LabeledGridSurface\` (plus their props/format-input types). Special-purpose surfaces with no docs page; we anticipate shape change. +- **Two deletions.** \`PretableCoreColumn\` alias (use \`PretableColumn\` from \`@pretable/core\` if the headless base is needed); \`PretableSurfaceSortDirection\` leak (replaced with \`PretableSortDirection\` from core). +- **TSDoc + \`@public\` on every public symbol** so \`react.api.md\` shows annotated API. +- **Per-package \`README.md\`** with the "when to reach for what" table. +- **8 website docs updated** for the hook rename. +- **Clipboard symbols stay \`@public\`.** Per user feedback: clipboard is stable API. Docs alignment captured in \`project_clipboard_docs_followup.md\` memory. + +## Test plan +- [x] \`pnpm -w typecheck\` clean +- [x] \`pnpm -w test\` clean +- [x] \`pnpm -w lint\` clean +- [x] \`pnpm format\` clean +- [x] \`pnpm api:check\` clean (all 4 packages) +- [x] \`react.api.md\` shows \`usePretable\` (returning \`PretableModel\`); no \`usePretableModel\` +- [x] \`react.api.md\` has \`ɵ\`-prefixed exports for the 3 demoted symbols +- [x] \`InspectionGrid\` / \`LabeledGridSurface\` annotated \`@beta\` +- [x] \`PretableCoreColumn\` and \`PretableSurfaceSortDirection\` gone + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +- [ ] **Step 4: Set auto-merge** + +```bash +gh pr merge --auto --squash +``` + +--- + +## Self-review checklist + +- **Spec coverage:** every audit decision in the spec table maps to a task. Hook rename = Task 1. PretableSurfaceSortDirection leak fix = Task 2. TSDoc + tags = Task 3. public_api.ts + index.ts collapse + PretableCoreColumn deletion = Task 4. react.api.md regen + diff audit = Task 5. README = Task 6. Gates + PR = Task 7. +- **Placeholder scan:** no `TBD`, `TODO`, "implement later", or "etc." in any task body. +- **Type/name consistency:** the hook name and options-type rename is consistent across Task 1 (rewrite), Task 4 (public_api.ts re-export), Task 5 (diff audit). The 3 ɵ-prefix renames are consistent across Task 3 (`@internal` tag) and Task 4 (re-export with rename). diff --git a/docs/superpowers/specs/2026-05-08-tier1-api-stabilization-pr3-react-design.md b/docs/superpowers/specs/2026-05-08-tier1-api-stabilization-pr3-react-design.md new file mode 100644 index 000000000..9e1c43a84 --- /dev/null +++ b/docs/superpowers/specs/2026-05-08-tier1-api-stabilization-pr3-react-design.md @@ -0,0 +1,163 @@ +# Tier 1 Sub-project A — PR 3 (`@pretable/react` audit) Design + +> Status: spec. 2026-05-08. PR 3 of 5 in [Tier 1 Sub-project A — Public API Stabilization](2026-05-07-tier1-public-api-stabilization-design.md). + +## Goal + +Lock `@pretable/react`'s public surface for 1.0. Move to the `public_api.ts` convention, demote internal-leakage exports to `ɵ`-prefix, tag long-lived experimental surfaces `@beta`, fix one forgotten-export leak, retire one alias, rename the public hook to a brevity-favoring name, and TSDoc every public symbol with `@public`. + +## Non-goals + +- No `@pretable/ui` density consolidation. PR 4 owns the `useResolvedHeights` ↔ `getDensityHeights` decision; this PR keeps the React hook (now `ɵ`-prefixed) untouched in shape. +- No copy/clipboard docs page. Clipboard symbols stay `@public`; the docs alignment is captured in `project_clipboard_docs_followup.md` memory and ships in a later docs-pass session. +- No removal of headless types from react's re-export surface beyond what the rename ripple forces. +- No new tests beyond what the rename + TSDoc ripple requires. + +## Architecture + +### Public-surface layout + +`@pretable/react/src/` becomes: + +``` +src/ + index.ts // export * from './public_api'; + public_api.ts // hand-curated re-exports, the entire public surface + ...component / hook / helper files (internal) +``` + +`public_api.ts` is the only file whose contents are reviewed for public-API impact. + +### Hook rename — brevity wins + +The current public surface ships two hooks, naming-inverted from how docs talk: + +- `usePretable(opts) → PretableGrid` — low-level (just memoizes `createGrid`). +- `usePretableModel(opts) → PretableModel` — full hook used by every doc and `PretableSurface` itself. + +The fix: + +- **Delete** the low-level `usePretable`. Its body is `useMemo(() => createGrid(opts), [opts])` — trivial to inline at call sites if anyone needs it. +- **Rename** `usePretableModel` → `usePretable`. The new `usePretable(opts: UsePretableOptions): PretableModel` is the only public hook. +- **Rename** `UsePretableModelOptions` → `UsePretableOptions`. The old `UsePretableOptions` (a strict subset) is deleted. + +Net: the simple-sounding name is the one users actually want. Pre-1.0, no backcompat shim. + +### Release tag policy + +- `@public` — stable for 1.0. Includes components, the renamed `usePretable`, render-input/output types, props interfaces, clipboard helpers and types. +- `@beta` — `InspectionGrid`, `LabeledGridSurface`, and their `*Props` / `*FormatValueInput` types. Special-purpose surfaces with no docs page; we anticipate shape change. +- `@internal` (with `ɵ`-prefix at the public re-export site) — `useResolvedHeights`, `measureRenderedRowHeight`, `ROW_SELECT_COLUMN_ID`. Cross-package internal use is supported (the `@pretable-internal/*` packages or future internal hooks may reach for them); external consumers shouldn't. + +The original symbol names stay clean inside the package; only the `public_api.ts` re-export wears the `ɵ`-prefix, e.g., + +```ts +export { + useResolvedHeights as ɵuseResolvedHeights, + measureRenderedRowHeight as ɵmeasureRenderedRowHeight, + ROW_SELECT_COLUMN_ID as ɵROW_SELECT_COLUMN_ID, +} from "..."; +``` + +### Forgotten-export fix + +`PretableSurfaceSortDirection` currently leaks (an `ae-forgotten-export` warning in PR 2's regenerated `react.api.md`). It's a local `"asc" | "desc" | null` alias inside `pretable-surface.tsx` that escapes through a prop type. Resolution: replace it with the canonical `PretableSortDirection` re-exported from `@pretable/core`, then delete the local alias. + +### Alias retirement + +`PretableCoreColumn` was kept in PR 2 as a re-export alias (`export type { PretableColumn as PretableCoreColumn } from "@pretable/core"`) to preserve react's surface during the engine rename. PR 3 deletes it. Users consume `PretableColumn` from `@pretable/react` (the extended type with `format`/`render`); if they need the engine-only base, they import `PretableColumn` from `@pretable/core` directly. + +## Components — full audit decisions + +| Symbol | Decision | Notes | +| ------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- | +| `Pretable` | `@public` | Wrapper around `PretableSurface`. Used in 4 website docs. | +| `PretableSurface` | `@public` | Primary controlled component. | +| `InspectionGrid` | `@beta` | Special-purpose surface; no docs page. | +| `LabeledGridSurface` | `@beta` | Same. | +| `usePretable` (old, returns `PretableGrid`) | **Delete** | Trivial; inline `useMemo` at call sites. | +| `usePretableModel` | **Rename → `usePretable`** | Returns `PretableModel`. | +| `useResolvedHeights` | `ɵ`-prefix at re-export | `@internal` tag on declaration. | +| `measureRenderedRowHeight` | `ɵ`-prefix at re-export | `@internal` tag on declaration. | +| `ROW_SELECT_COLUMN_ID` | `ɵ`-prefix at re-export | `@internal` tag on declaration. | +| `defaultCoerceForCopy` | `@public` | Per user feedback — clipboard story is stable. | +| `serializeRangesAsTsv` | `@public` | Per user feedback. | +| `CopyPayload` | `@public` | Per user feedback. | +| `SerializeRangesArgs` | `@public` | Per user feedback. | +| `PretableColumn` | `@public` | React-extended (with `format`/`render`); imports core's `PretableColumn` as `PretableBaseColumn`. | +| `PretableCoreColumn` (alias) | **Delete** | Use `PretableColumn` from `@pretable/core` if the base is needed. | +| `PretableSurfaceSortDirection` (leak) | **Delete** | Replace usage with `PretableSortDirection` from core. | +| `PretableModel` | `@public` | Hook output. TSDoc clarifies role. | +| `PretableRenderSnapshot` | `@public` | Render-layout output. TSDoc clarifies role. | +| `PretableRenderRow` | `@public` | Member of render snapshot. | +| `PretableTelemetry` | `@public` | Hook output. | +| `PretableSurfaceState` | `@public` | **Input** (controlled state shape). TSDoc explicitly says "controlled state input". | +| `PretableProps` | `@public` | Component props. | +| `PretableSurfaceProps` | `@public` | Component props. | +| `PretableSurfaceMessages` | `@public` | i18n messages. | +| `RowSelectionColumnConfig` | `@public` | Surface prop config. | +| `InspectionGridProps` | `@beta` | Tracks `InspectionGrid`. | +| `LabeledGridSurfaceProps` | `@beta` | Tracks `LabeledGridSurface`. | +| `LabeledGridSurfaceFormatValueInput` | `@beta` | Tracks `LabeledGridSurface`. | +| `PretableCellRenderInput` | `@public` | Render-input shape. | +| `PretableHeaderRenderInput` | `@public` | Header render-input shape. | +| `PretableFormatInput` | `@public` (re-export from core) | Already `@public` in core. | +| `UsePretableOptions` (renamed from `UsePretableModelOptions`) | `@public` | New canonical hook options. | +| `DensityHeights` | `@public` | Density type. PR 4 may consolidate the source-of-truth question. | + +Re-exports from `@pretable/core` continue to flow through react's public surface: `PretableGrid`, `PretableGridOptions`, `PretableGridSnapshot`, `PretableRow`. (No `PretableCoreColumn` re-export — deleted.) + +## Data flow + +1. Apply audit decisions in source files (rename, delete, demote, tag). +2. Update internal consumers (`pretable-surface.tsx`, test files, website docs). +3. Write `public_api.ts` listing every public + `ɵ`-prefixed symbol with TSDoc on declarations as needed. +4. Collapse `index.ts` to `export * from './public_api'`. +5. Regenerate `react.api.md`. The diff is substantial (rename + 5 demotions + 1 deletion + 4 `@beta` tags + many new TSDoc summaries) but bounded. +6. Verify all gates including bench (which uses `PretableSurface`, not the renamed hook — should pass without change). + +## Error handling + +- **Internal compile errors after rename.** Caught by `pnpm -w typecheck`. Fix inline. +- **Website docs broken by hook rename.** `apps/website` typecheck and the website's vitest tests catch this; the rename in `grid/index.mdx` (and any other doc using `usePretableModel`) is mechanical. +- **`react.api.md` shows unexpected new/removed symbols.** Audit the diff against this spec's table. Mismatch = bug. + +## Testing + +Repo-wide gates: `pnpm -w typecheck`, `pnpm -w test`, `pnpm -w lint`, `pnpm format`, `pnpm api:check`. The two react test files (`pretable-surface.test.tsx`, `pretable.test.tsx`) need their `usePretableModel` → `usePretable` rename. No new test categories. + +## Per-package README + +`packages/react/README.md` ~80 lines: + +- One-paragraph "what is `@pretable/react`" — the React surface for pretable. +- "When to use" — pick `` for drop-in, `` for controlled, the `usePretable` hook for custom rendering. +- Install + minimal `` example. +- "See [`react.api.md`](./react.api.md) for the full surface." +- License/contributing footer matching repo style. + +## PR shape + +Single PR. The rename is atomic — splitting would force website docs to compile against half-renamed hooks. + +Implementation tasks (subagent-driven): + +1. Hook rename: rewrite `use-pretable.ts` (delete old `usePretable`, rename `usePretableModel` → `usePretable`, rename options type); update `pretable-surface.tsx`; update 2 react test files. +2. Forgotten-export fix: replace `PretableSurfaceSortDirection` with `PretableSortDirection` re-export from core; delete the local alias. +3. Symbol audit: add `@public` / `@beta` / `@internal` TSDoc on every declaration in `pretable.tsx`, `pretable-surface.tsx`, `inspection-grid.tsx`, `labeled-grid-surface.tsx`, `density.ts`, `row-height.ts`, `copy.ts`, `constants.ts`, `use-pretable.ts`, `types.ts`. +4. Write `packages/react/src/public_api.ts` (curated re-exports, including `ɵ`-prefix renames at re-export site); collapse `index.ts` to one line; remove `PretableCoreColumn` re-export. +5. Update website docs: `apps/website/content/docs/grid/index.mdx` (rename `usePretableModel` → `usePretable`, `UsePretableModelOptions` → `UsePretableOptions`). +6. Regenerate `react.api.md`; audit the diff against this spec. +7. Write `packages/react/README.md`. +8. Repo-wide gates + PR. + +## Success criteria + +- `react.api.md` has zero `ae-forgotten-export` warnings. +- Every public symbol in `react.api.md` shows `@public` (not `@public (undocumented)`) at the type level. Member-level `(undocumented)` is acceptable. +- `@beta` annotation present on `InspectionGrid`, `LabeledGridSurface`, and their props/render-input types. +- Three new `ɵ`-prefixed exports in `react.api.md`: `ɵuseResolvedHeights`, `ɵmeasureRenderedRowHeight`, `ɵROW_SELECT_COLUMN_ID`. The non-prefixed names are gone. +- `usePretable` (returns `PretableModel`) is the only hook export; `usePretableModel` is gone. +- `PretableCoreColumn` and `PretableSurfaceSortDirection` are gone from `react.api.md`. +- `packages/react/README.md` exists with the structure above. +- All repo-wide gates pass. diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 000000000..e3efffe43 --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,50 @@ +# @pretable/react + +The React surface for [pretable](https://pretable.dev/). Ships three components and one hook on top of `@pretable/core`'s headless engine. + +## When to reach for what + +- **``** — drop-in. Pass `columns` and `rows` and you're done. Best for quick wins. +- **``** — controlled. Pass `state` to drive sort, filter, selection, focus, or column layout from the outside. Best for production apps. +- **`usePretable`** — the hook. Returns `{ grid, snapshot, renderSnapshot, telemetry }` for custom rendering. Best when you need more control than `` provides — e.g., a non-default DOM layout or a different framework. + +`` and `` are special-purpose surfaces tagged `@beta` — they work, but expect shape changes pre-1.0. + +## Install + +```sh +npm install @pretable/react @pretable/core @pretable/ui +# or pnpm add … / yarn add … +``` + +`@pretable/ui` ships the CSS theme; import the stylesheet once at the root of your app. + +## Minimal example + +```tsx +import { Pretable } from "@pretable/react"; +import "@pretable/ui/grid.css"; + +function App() { + return ( + + ); +} +``` + +## Full public surface + +See **[`react.api.md`](./react.api.md)** for every exported component, hook, type, and function with their full signatures. The file is generated by [API Extractor](https://api-extractor.com/) and committed to the repo; CI fails if it drifts. + +## License + +MIT — see [LICENSE](../../LICENSE). diff --git a/packages/react/react.api.md b/packages/react/react.api.md index c814cbbb6..1d3878bc4 100644 --- a/packages/react/react.api.md +++ b/packages/react/react.api.md @@ -9,7 +9,7 @@ import { HTMLAttributes } from 'react'; import * as react_jsx_runtime from 'react/jsx-runtime'; import { ReactNode } from 'react'; -// @public (undocumented) +// @public export interface CopyPayload { // (undocumented) html?: string; @@ -17,10 +17,10 @@ export interface CopyPayload { text: string; } -// @public (undocumented) +// @public export function defaultCoerceForCopy(value: unknown): string; -// @public (undocumented) +// @public export interface DensityHeights { // (undocumented) headerHeight: number; @@ -28,10 +28,10 @@ export interface DensityHeights { rowHeight: number; } -// @public (undocumented) +// @beta export function InspectionGrid(input: InspectionGridProps): react_jsx_runtime.JSX.Element; -// @public (undocumented) +// @beta export interface InspectionGridProps { // (undocumented) ariaLabel: string; @@ -79,10 +79,10 @@ export interface InspectionGridProps { viewportHeight: number; } -// @public (undocumented) +// @beta export function LabeledGridSurface(input: LabeledGridSurfaceProps): react_jsx_runtime.JSX.Element; -// @public (undocumented) +// @beta export interface LabeledGridSurfaceFormatValueInput { // (undocumented) column: PretableColumn; @@ -92,7 +92,7 @@ export interface LabeledGridSurfaceFormatValueInput { // (undocumented) ariaLabel: string; @@ -111,7 +111,7 @@ export interface LabeledGridSurfaceProps // (undocumented) getHeaderCellProps?: (input: { column: PretableColumn; - sortDirection: PretableSurfaceSortDirection; + sortDirection: PretableSortDirection; }) => HTMLAttributes | undefined; // (undocumented) getRowId?: PretableGridOptions["getRowId"]; @@ -161,13 +161,10 @@ export interface LabeledGridSurfaceProps viewportHeight: number; } -// @public (undocumented) -export function measureRenderedRowHeight(row: HTMLElement): number; - -// @public (undocumented) +// @public export function Pretable(input: PretableProps): react_jsx_runtime.JSX.Element; -// @public (undocumented) +// @public export interface PretableCellRenderInput extends PretableFormatInput { // (undocumented) formattedValue: string; @@ -181,48 +178,20 @@ export interface PretableCellRenderInput rowIndex: number; } -// @public (undocumented) -export interface PretableColumn extends PretableCoreColumn { +// Warning: (ae-forgotten-export) The symbol "PretableColumn_2" needs to be exported by the entry point index.d.ts +// +// @public +export interface PretableColumn extends PretableColumn_2 { // (undocumented) render?: (input: PretableCellRenderInput) => ReactNode; // (undocumented) renderHeader?: (input: PretableHeaderRenderInput) => ReactNode; } -// @public -export interface PretableCoreColumn { - // (undocumented) - filterable?: boolean; - // (undocumented) - format?: (input: PretableFormatInput) => string; - // (undocumented) - header?: string; - // (undocumented) - id: string; - // (undocumented) - maxWidthPx?: number; - // (undocumented) - minWidthPx?: number; - // (undocumented) - pinned?: "left"; - // (undocumented) - reorderable?: boolean; - // (undocumented) - resizable?: boolean; - // (undocumented) - sortable?: boolean; - // (undocumented) - value?: (row: TRow) => unknown; - // (undocumented) - widthPx?: number; - // (undocumented) - wrap?: boolean; -} - // @public export interface PretableFormatInput { // (undocumented) - column: PretableCoreColumn; + column: PretableColumn_2; // (undocumented) row: TRow; // (undocumented) @@ -256,7 +225,7 @@ export interface PretableGrid { getSnapshot(): PretableGridSnapshot; readonly kind: "pretable-grid"; // (undocumented) - mergeColumnsFromProps(nextColumns: PretableCoreColumn[]): void; + mergeColumnsFromProps(nextColumns: PretableColumn_2[]): void; // (undocumented) moveColumn(columnId: string, toIndex: number): void; // Warning: (ae-forgotten-export) The symbol "PretableFocusDirection" needs to be exported by the entry point index.d.ts @@ -285,8 +254,6 @@ export interface PretableGrid { // // (undocumented) setSelection(state: PretableSelectionState): void; - // Warning: (ae-forgotten-export) The symbol "PretableSortDirection" needs to be exported by the entry point index.d.ts - // // (undocumented) setSort(columnId: string | null, direction: PretableSortDirection): void; // Warning: (ae-forgotten-export) The symbol "PretableViewportState" needs to be exported by the entry point index.d.ts @@ -303,7 +270,7 @@ export interface PretableGridOptions { // (undocumented) autosize?: boolean | AutosizeOptions; // (undocumented) - columns: PretableCoreColumn[]; + columns: PretableColumn_2[]; // (undocumented) getRowId?: (row: TRow, index: number) => string; // (undocumented) @@ -338,7 +305,7 @@ export interface PretableGridSnapshot { visibleRows: PretableVisibleRow[]; } -// @public (undocumented) +// @public export interface PretableHeaderRenderInput { // (undocumented) column: PretableColumn; @@ -350,7 +317,7 @@ export interface PretableHeaderRenderInput { // (undocumented) grid: PretableGrid; @@ -362,7 +329,7 @@ export interface PretableModel { telemetry: PretableTelemetry; } -// @public (undocumented) +// @public export interface PretableProps { // (undocumented) columns: PretableColumn[]; @@ -390,7 +357,7 @@ export interface PretableProps { tabBehavior?: PretableSurfaceProps["tabBehavior"]; } -// @public (undocumented) +// @public export interface PretableRenderRow { // (undocumented) height: number; @@ -404,7 +371,7 @@ export interface PretableRenderRow { top: number; } -// @public (undocumented) +// @public export interface PretableRenderSnapshot { // Warning: (ae-forgotten-export) The symbol "PlannedColumn" needs to be exported by the entry point index.d.ts // @@ -423,10 +390,10 @@ export interface PretableRenderSnapshot // @public export type PretableRow = Record; -// @public (undocumented) +// @public export function PretableSurface(input: PretableSurfaceProps): react_jsx_runtime.JSX.Element; -// @public (undocumented) +// @public export interface PretableSurfaceMessages { // (undocumented) copyAnnouncement?: (args: { @@ -443,7 +410,7 @@ export interface PretableSurfaceMessages { }) => string; } -// @public (undocumented) +// @public export interface PretableSurfaceProps { // (undocumented) ariaLabel: string; @@ -526,7 +493,7 @@ export interface PretableSurfaceProps { viewportStyle?: CSSProperties; } -// @public (undocumented) +// @public export interface PretableSurfaceState { // (undocumented) columnOrder?: readonly string[]; @@ -544,7 +511,7 @@ export interface PretableSurfaceState { sort?: PretableSortState | null; } -// @public (undocumented) +// @public export interface PretableTelemetry { // (undocumented) focusedRowId: string | null; @@ -567,10 +534,7 @@ export interface PretableTelemetry { }; } -// @public (undocumented) -export const ROW_SELECT_COLUMN_ID = "__pretable_row_select__"; - -// @public (undocumented) +// @public export interface RowSelectionColumnConfig { // (undocumented) enabled: true; @@ -584,7 +548,7 @@ export interface RowSelectionColumnConfig { width?: number; } -// @public (undocumented) +// @public export interface SerializeRangesArgs { // (undocumented) columns: readonly PretableColumn[]; @@ -596,17 +560,20 @@ export interface SerializeRangesArgs { visibleRows: readonly PretableVisibleRow[]; } -// @public (undocumented) +// @public export function serializeRangesAsTsv(args: SerializeRangesArgs): CopyPayload | null; -// @public (undocumented) -export function usePretable(input: UsePretableOptions): PretableGrid; - -// @public (undocumented) -export function usePretableModel(input: UsePretableModelOptions): PretableModel; +// @public +export function usePretable(input: UsePretableOptions): PretableModel; -// @public (undocumented) -export interface UsePretableModelOptions extends UsePretableOptions { +// @public +export interface UsePretableOptions { + // (undocumented) + autosize?: boolean | AutosizeOptions; + // (undocumented) + columns: PretableColumn[]; + // (undocumented) + getRowId?: PretableGridOptions["getRowId"]; // (undocumented) measuredHeights?: Record; // (undocumented) @@ -616,6 +583,8 @@ export interface UsePretableModelOptions // (undocumented) overscan?: number; // (undocumented) + rows: TRow[]; + // (undocumented) state?: PretableSurfaceState | null; // (undocumented) viewportHeight: number; @@ -623,24 +592,24 @@ export interface UsePretableModelOptions viewportWidth?: number; } -// @public (undocumented) -export interface UsePretableOptions { - // (undocumented) - autosize?: boolean | AutosizeOptions; - // (undocumented) - columns: PretableColumn[]; - // (undocumented) - getRowId?: PretableGridOptions["getRowId"]; - // (undocumented) - rows: TRow[]; -} +// Warning: (ae-internal-missing-underscore) The name "ɵmeasureRenderedRowHeight" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export function ɵmeasureRenderedRowHeight(row: HTMLElement): number; -// @public -export function useResolvedHeights(rowHeightProp?: number, headerHeightProp?: number): DensityHeights; +// Warning: (ae-internal-missing-underscore) The name "ɵROW_SELECT_COLUMN_ID" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export const ɵROW_SELECT_COLUMN_ID = "__pretable_row_select__"; + +// Warning: (ae-internal-missing-underscore) The name "ɵuseResolvedHeights" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export function ɵuseResolvedHeights(rowHeightProp?: number, headerHeightProp?: number): DensityHeights; // Warnings were encountered during analysis: // -// dist/index.d.ts:295:9 - (ae-forgotten-export) The symbol "PretableSurfaceSortDirection" needs to be exported by the entry point index.d.ts +// dist/index.d.ts:435:9 - (ae-forgotten-export) The symbol "PretableSortDirection" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/react/src/__tests__/pretable-surface.test.tsx b/packages/react/src/__tests__/pretable-surface.test.tsx index ad9c400d1..ceda90879 100644 --- a/packages/react/src/__tests__/pretable-surface.test.tsx +++ b/packages/react/src/__tests__/pretable-surface.test.tsx @@ -18,7 +18,7 @@ import { } from "../pretable-surface"; import type { CopyPayload, SerializeRangesArgs } from "../copy"; import * as rowHeight from "../row-height"; -import { type PretableSurfaceState, usePretableModel } from "../use-pretable"; +import { type PretableSurfaceState, usePretable } from "../use-pretable"; import type { PretableFocusState, PretableGrid, @@ -81,9 +81,9 @@ const rows: DemoRow[] = [ const getDemoRowId = (row: DemoRow) => row.id; describe("PretableSurface", () => { - it("exposes renderer telemetry from usePretableModel for internal consumers", () => { + it("exposes renderer telemetry from usePretable for internal consumers", () => { function Harness() { - const model = usePretableModel({ + const model = usePretable({ columns, getRowId: getDemoRowId, overscan: 0, diff --git a/packages/react/src/__tests__/pretable.test.tsx b/packages/react/src/__tests__/pretable.test.tsx index 12fb42861..d3890b0fa 100644 --- a/packages/react/src/__tests__/pretable.test.tsx +++ b/packages/react/src/__tests__/pretable.test.tsx @@ -3,7 +3,8 @@ import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"; import { afterEach, expect, it, vi } from "vitest"; import { useEffect } from "react"; -import { Pretable, measureRenderedRowHeight, usePretableModel } from "../index"; +import { Pretable, usePretable } from "../index"; +import { measureRenderedRowHeight } from "../row-height"; afterEach(() => { cleanup(); @@ -290,7 +291,7 @@ it("exposes a public render model hook that reacts to grid viewport updates", () ]; const getRowId = (row: { id: string }) => row.id; const HookProbe = () => { - const model = usePretableModel({ + const model = usePretable({ columns, getRowId, rows, @@ -343,7 +344,7 @@ it("plans and reports visible rows from the provided body viewport height", () = const getRowId = (row: { id: string }) => row.id; const HookProbe = () => { - const model = usePretableModel({ + const model = usePretable({ columns, getRowId, rows, diff --git a/packages/react/src/constants.ts b/packages/react/src/constants.ts index 7d2668f05..169743878 100644 --- a/packages/react/src/constants.ts +++ b/packages/react/src/constants.ts @@ -1 +1,7 @@ +/** + * Reserved column id for the synthetic row-select checkbox column. + * Internal — surface authors shouldn't reference this directly. + * + * @internal + */ export const ROW_SELECT_COLUMN_ID = "__pretable_row_select__"; diff --git a/packages/react/src/copy.ts b/packages/react/src/copy.ts index eb98d762c..de57f1a14 100644 --- a/packages/react/src/copy.ts +++ b/packages/react/src/copy.ts @@ -7,6 +7,11 @@ import type { import { ROW_SELECT_COLUMN_ID } from "./constants"; import type { PretableColumn } from "./types"; +/** + * Input for {@link serializeRangesAsTsv}. + * + * @public + */ export interface SerializeRangesArgs { ranges: readonly PretableCellRange[]; visibleRows: readonly PretableVisibleRow[]; @@ -14,11 +19,21 @@ export interface SerializeRangesArgs { copyWithHeaders?: boolean; } +/** + * Plain-text + HTML pair returned by clipboard serializers and consumed by `onCopy` / `copyToClipboard` props. + * + * @public + */ export interface CopyPayload { text: string; html?: string; } +/** + * Default coerce-value-to-string used during clipboard serialization. Useful as a fallback inside custom serializers. + * + * @public + */ export function defaultCoerceForCopy(value: unknown): string { if (value === null || value === undefined) return ""; if (value instanceof Date) return value.toISOString(); @@ -36,6 +51,11 @@ export function defaultCoerceForCopy(value: unknown): string { return String(value); } +/** + * Serialize one or more `PretableCellRange`s to a tab-separated text + HTML payload suitable for clipboard write. + * + * @public + */ export function serializeRangesAsTsv( args: SerializeRangesArgs, ): CopyPayload | null { diff --git a/packages/react/src/density.ts b/packages/react/src/density.ts index 41b55ce9b..d444b5dee 100644 --- a/packages/react/src/density.ts +++ b/packages/react/src/density.ts @@ -12,6 +12,11 @@ const FALLBACK_ROW_HEIGHT = 32; // is irrelevant. const FALLBACK_HEADER_HEIGHT = HEADER_HEIGHT; +/** + * CSS-token-derived heights used by `` / `` to size header and rows. PR 4 may consolidate the source of truth between this package and `@pretable/ui`. + * + * @public + */ export interface DensityHeights { rowHeight: number; headerHeight: number; @@ -66,18 +71,9 @@ function subscribe(callback: () => void): () => void { } /** - * React hook — reactive density values that update when `[data-density]`, - * `[data-theme]`, `class`, or inline `style` change on ``. - * - * Numeric props win when passed; otherwise CSS variables; otherwise fallbacks. - * - * Currently the engine only uses `headerHeight` (replaces the legacy - * HEADER_HEIGHT constant). The `rowHeight` value is exposed for API parity - * with the spec's documented contract and for future use; row sizing in v0.0.1 - * remains measurement-driven via `measureRenderedRowHeight()` / - * `estimateRowHeight()`. + * React hook returning the current density heights derived from the active CSS theme. Internal — `` and `` use this; external consumers should reach for `getDensityHeights` from `@pretable/ui` when PR 4 lands. * - * SSR-safe: server snapshot returns fallback values without DOM access. + * @internal */ export function useResolvedHeights( rowHeightProp?: number, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 5209d0483..4ef9ceca4 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,61 +1 @@ -// Components -export { Pretable } from "./pretable"; -export { PretableSurface } from "./pretable-surface"; -export { InspectionGrid } from "./inspection-grid"; -export { LabeledGridSurface } from "./labeled-grid-surface"; - -// Hooks -export { usePretable, usePretableModel } from "./use-pretable"; -export { useResolvedHeights } from "./density"; - -// Helpers -export { measureRenderedRowHeight } from "./row-height"; - -// Component prop types -export type { PretableProps } from "./pretable"; -export type { - PretableSurfaceMessages, - PretableSurfaceProps, - RowSelectionColumnConfig, -} from "./pretable-surface"; -export { ROW_SELECT_COLUMN_ID } from "./pretable-surface"; - -// Copy / clipboard -export { defaultCoerceForCopy, serializeRangesAsTsv } from "./copy"; -export type { CopyPayload, SerializeRangesArgs } from "./copy"; -export type { InspectionGridProps } from "./inspection-grid"; -export type { - LabeledGridSurfaceFormatValueInput, - LabeledGridSurfaceProps, -} from "./labeled-grid-surface"; - -// Hook + model types -export type { - PretableModel, - PretableRenderRow, - PretableRenderSnapshot, - PretableSurfaceState, - PretableTelemetry, - UsePretableModelOptions, - UsePretableOptions, -} from "./use-pretable"; - -// Density -export type { DensityHeights } from "./density"; - -// Re-exports from @pretable/core -export type { - PretableColumn as PretableCoreColumn, - PretableGrid, - PretableGridOptions, - PretableGridSnapshot, - PretableRow, -} from "@pretable/core"; - -// React-extended column type + render-input shapes -export type { - PretableCellRenderInput, - PretableColumn, - PretableFormatInput, - PretableHeaderRenderInput, -} from "./types"; +export * from "./public_api"; diff --git a/packages/react/src/inspection-grid.tsx b/packages/react/src/inspection-grid.tsx index ba39c93ed..68b560938 100644 --- a/packages/react/src/inspection-grid.tsx +++ b/packages/react/src/inspection-grid.tsx @@ -18,6 +18,11 @@ const filterableHeaderProps = { "data-filterable": "true", } as HTMLAttributes; +/** + * Props for {@link InspectionGrid}. + * + * @beta + */ export interface InspectionGridProps { ariaLabel: string; filterableColumnIds: readonly InspectionFilterableColumnId[]; @@ -41,6 +46,11 @@ export interface InspectionGridProps { viewportHeight: number; } +/** + * Special-purpose inspection surface that renders rows as labeled key/value pairs. Experimental — shape may change before 1.0. + * + * @beta + */ export function InspectionGrid({ ariaLabel, filterableColumnIds, diff --git a/packages/react/src/labeled-grid-surface.tsx b/packages/react/src/labeled-grid-surface.tsx index 55b72de98..b05df1dc7 100644 --- a/packages/react/src/labeled-grid-surface.tsx +++ b/packages/react/src/labeled-grid-surface.tsx @@ -1,10 +1,19 @@ -import type { PretableGridOptions, PretableRow } from "@pretable/core"; +import type { + PretableGridOptions, + PretableRow, + PretableSortDirection, +} from "@pretable/core"; import type { HTMLAttributes } from "react"; import type { PretableTelemetry } from "./use-pretable"; import { type PretableSurfaceProps, PretableSurface } from "./pretable-surface"; import type { PretableColumn } from "./types"; +/** + * Input passed to a {@link LabeledGridSurface} format function. + * + * @beta + */ export interface LabeledGridSurfaceFormatValueInput< TRow extends PretableRow = PretableRow, > { @@ -13,6 +22,11 @@ export interface LabeledGridSurfaceFormatValueInput< value: unknown; } +/** + * Props for {@link LabeledGridSurface}. + * + * @beta + */ export interface LabeledGridSurfaceProps< TRow extends PretableRow = PretableRow, > { @@ -25,7 +39,7 @@ export interface LabeledGridSurfaceProps< ) => HTMLAttributes | undefined; getHeaderCellProps?: (input: { column: PretableColumn; - sortDirection: PretableSurfaceSortDirection; + sortDirection: PretableSortDirection; }) => HTMLAttributes | undefined; getRowId?: PretableGridOptions["getRowId"]; headerCellClassName?: string; @@ -54,6 +68,11 @@ export interface LabeledGridSurfaceProps< viewportHeight: number; } +/** + * Special-purpose surface for label/value-style table layouts. Experimental — shape may change before 1.0. + * + * @beta + */ export function LabeledGridSurface({ ariaLabel, bodyCellClassName, @@ -213,9 +232,3 @@ function formatDefaultValue(value: unknown) { return String(value ?? ""); } - -type PretableSurfaceSortDirection = NonNullable< - Parameters< - NonNullable - >[0]["sortDirection"] -> | null; diff --git a/packages/react/src/pretable-surface.tsx b/packages/react/src/pretable-surface.tsx index c0cddc245..b041abe4f 100644 --- a/packages/react/src/pretable-surface.tsx +++ b/packages/react/src/pretable-surface.tsx @@ -35,7 +35,7 @@ import { measureRenderedRowHeight } from "./row-height"; import { type PretableSurfaceState, type PretableTelemetry, - usePretableModel, + usePretable, } from "./use-pretable"; import { useResolvedHeights } from "./density"; import { @@ -81,6 +81,11 @@ async function defaultCopyToClipboard(payload: CopyPayload): Promise { } } +/** + * Configuration for the synthetic row-select column rendered by {@link PretableSurface} when `rowSelectionColumn` is enabled. + * + * @public + */ export interface RowSelectionColumnConfig { enabled: true; position?: "left"; @@ -89,6 +94,11 @@ export interface RowSelectionColumnConfig { width?: number; } +/** + * Localizable user-facing strings rendered by {@link PretableSurface}. Pass to override the English defaults. + * + * @public + */ export interface PretableSurfaceMessages { selectAllAnnouncement?: (args: { rowCount: number; @@ -167,6 +177,11 @@ interface PretableSurfaceRowAttributesInput< rowIndex: number; } +/** + * Props for {@link PretableSurface}. + * + * @public + */ export interface PretableSurfaceProps { ariaLabel: string; autosize?: boolean | AutosizeOptions; @@ -379,6 +394,11 @@ function headerContentPropsEqual( const MemoizedHeaderContent = memo(HeaderContentImpl, headerContentPropsEqual); +/** + * Controlled grid surface. The primary React component. Pass `state` to control any subset of sort/filter/selection/focus/column-layout from the outside; omit slices you want the grid to own. + * + * @public + */ export function PretableSurface({ ariaLabel, autosize, @@ -507,7 +527,7 @@ export function PretableSurface({ }; return [synth, ...columns]; }, [columns, rowSelectionColumn]); - const { grid, snapshot, renderSnapshot, telemetry } = usePretableModel({ + const { grid, snapshot, renderSnapshot, telemetry } = usePretable({ autosize, columns: effectiveColumns, getRowId, diff --git a/packages/react/src/pretable.tsx b/packages/react/src/pretable.tsx index 3f8014e2f..c9175efe7 100644 --- a/packages/react/src/pretable.tsx +++ b/packages/react/src/pretable.tsx @@ -3,6 +3,11 @@ import { type PretableGridOptions, type PretableRow } from "@pretable/core"; import { type PretableSurfaceProps, PretableSurface } from "./pretable-surface"; import type { PretableColumn } from "./types"; +/** + * Props for the {@link Pretable} drop-in component. + * + * @public + */ export interface PretableProps { columns: PretableColumn[]; getRowId?: PretableGridOptions["getRowId"]; @@ -27,6 +32,11 @@ const BENCHMARK_VIEWPORT_STYLE = { overscrollBehavior: "contain", } as const; +/** + * Drop-in pretable component. Wraps {@link PretableSurface} with internal state — pass `columns` and `rows` and you're done. Reach for `PretableSurface` when you need to control state from the outside. + * + * @public + */ export function Pretable({ columns, getRowId, diff --git a/packages/react/src/public_api.ts b/packages/react/src/public_api.ts new file mode 100644 index 000000000..03b2b9194 --- /dev/null +++ b/packages/react/src/public_api.ts @@ -0,0 +1,69 @@ +/** + * Public API of `@pretable/react`. Hand-curated re-exports — do not edit + * `index.ts` directly. Internal symbols stay in their source files and are + * re-exported here under the `ɵ`-prefix when other `@pretable/*` packages + * (or future internal hooks) need them. + * + * @packageDocumentation + */ + +// Components +export { Pretable } from "./pretable"; +export { PretableSurface } from "./pretable-surface"; +export { InspectionGrid } from "./inspection-grid"; +export { LabeledGridSurface } from "./labeled-grid-surface"; + +// Hooks +export { usePretable } from "./use-pretable"; + +// Component prop / message / config types +export type { PretableProps } from "./pretable"; +export type { + PretableSurfaceMessages, + PretableSurfaceProps, + RowSelectionColumnConfig, +} from "./pretable-surface"; +export type { InspectionGridProps } from "./inspection-grid"; +export type { + LabeledGridSurfaceFormatValueInput, + LabeledGridSurfaceProps, +} from "./labeled-grid-surface"; + +// Hook input + output shapes +export type { + PretableModel, + PretableRenderRow, + PretableRenderSnapshot, + PretableSurfaceState, + PretableTelemetry, + UsePretableOptions, +} from "./use-pretable"; + +// React-extended column type + render-input shapes +export type { + PretableCellRenderInput, + PretableColumn, + PretableFormatInput, + PretableHeaderRenderInput, +} from "./types"; + +// Copy / clipboard +export { defaultCoerceForCopy, serializeRangesAsTsv } from "./copy"; +export type { CopyPayload, SerializeRangesArgs } from "./copy"; + +// Density +export type { DensityHeights } from "./density"; + +// Re-exports from @pretable/core (the engine types react users typically +// touch — full headless surface lives in @pretable/core) +export type { + PretableGrid, + PretableGridOptions, + PretableGridSnapshot, + PretableRow, +} from "@pretable/core"; + +// Internal-but-exported (ɵ-prefix marks these as not API-stable) +export { useResolvedHeights as ɵuseResolvedHeights } from "./density"; +export { measureRenderedRowHeight as ɵmeasureRenderedRowHeight } from "./row-height"; +export { ROW_SELECT_COLUMN_ID as ɵROW_SELECT_COLUMN_ID } from "./constants"; diff --git a/packages/react/src/row-height.ts b/packages/react/src/row-height.ts index 4b44a0e3f..8340f6c78 100644 --- a/packages/react/src/row-height.ts +++ b/packages/react/src/row-height.ts @@ -8,6 +8,11 @@ function parsePxLength(value: string | null | undefined): number { return Number.isFinite(parsed) ? parsed : 0; } +/** + * DOM measurement helper used internally by the surface's row-height accounting. Not part of the user-facing API. + * + * @internal + */ export function measureRenderedRowHeight(row: HTMLElement) { const style = getComputedStyle(row); const verticalPadding = diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 2e578bd6d..b0aff16f6 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -5,6 +5,11 @@ import type { PretableRow, } from "@pretable/core"; +/** + * React-extended column definition. Adds the `render` and `renderHeader` JSX-typed callbacks on top of `@pretable/core`'s base column. + * + * @public + */ export interface PretableColumn< TRow extends PretableRow = PretableRow, > extends PretableBaseColumn { @@ -12,6 +17,11 @@ export interface PretableColumn< renderHeader?: (input: PretableHeaderRenderInput) => ReactNode; } +/** + * Input passed to a column's `render` function. + * + * @public + */ export interface PretableCellRenderInput< TRow extends PretableRow = PretableRow, > extends PretableFormatInput { @@ -22,6 +32,11 @@ export interface PretableCellRenderInput< isSelected: boolean; } +/** + * Input passed to a column's `renderHeader` function. + * + * @public + */ export interface PretableHeaderRenderInput< TRow extends PretableRow = PretableRow, > { diff --git a/packages/react/src/use-pretable.ts b/packages/react/src/use-pretable.ts index cdd3e4b78..fdd21ab46 100644 --- a/packages/react/src/use-pretable.ts +++ b/packages/react/src/use-pretable.ts @@ -16,13 +16,11 @@ import { } from "@pretable-internal/renderer-dom"; import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react"; -export interface UsePretableOptions { - autosize?: boolean | AutosizeOptions; - columns: PretableColumn[]; - rows: TRow[]; - getRowId?: PretableGridOptions["getRowId"]; -} - +/** + * One row of layout-derived render state for use during custom rendering. + * + * @public + */ export interface PretableRenderRow { id: string; row: TRow; @@ -31,6 +29,13 @@ export interface PretableRenderRow { height: number; } +/** + * Layout-derived render snapshot returned by {@link usePretable}. Drives + * positioned-cell rendering — every column has a left + width, every visible + * row has a top + height. + * + * @public + */ export interface PretableRenderSnapshot< TRow extends PretableRow = PretableRow, > { @@ -41,6 +46,12 @@ export interface PretableRenderSnapshot< totalWidth: number; } +/** + * Telemetry numbers about the current render — counts and ranges suitable + * for status bars, dev panels, or virtualization debugging. + * + * @public + */ export interface PretableTelemetry { focusedRowId: string | null; rowModelRowCount: number; @@ -55,6 +66,12 @@ export interface PretableTelemetry { }; } +/** + * **Input** shape for controlling a {@link PretableSurface} from the outside. + * Pass the slices you want to control; omit slices you want the grid to own. + * + * @public + */ export interface PretableSurfaceState { filters?: Record; focus?: PretableFocusState; @@ -65,9 +82,16 @@ export interface PretableSurfaceState { columnPinned?: Record; } -export interface UsePretableModelOptions< - TRow extends PretableRow = PretableRow, -> extends UsePretableOptions { +/** + * Options for the {@link usePretable} hook. + * + * @public + */ +export interface UsePretableOptions { + autosize?: boolean | AutosizeOptions; + columns: PretableColumn[]; + rows: TRow[]; + getRowId?: PretableGridOptions["getRowId"]; viewportHeight: number; viewportWidth?: number; overscan?: number; @@ -77,6 +101,12 @@ export interface UsePretableModelOptions< onFocusChange?: (next: PretableFocusState) => void; } +/** + * Output of the {@link usePretable} hook — a stable handle plus the latest + * snapshot, render layout, and telemetry. + * + * @public + */ export interface PretableModel { grid: PretableGrid; snapshot: PretableGridSnapshot; @@ -84,23 +114,28 @@ export interface PretableModel { telemetry: PretableTelemetry; } +/** + * The primary React hook. Creates a grid, applies optional controlled state, + * and returns the latest snapshot, layout-derived render snapshot, and + * telemetry. Suitable for custom rendering — `` itself is + * built on top of this hook. + * + * @example + * ```tsx + * const { grid, snapshot, renderSnapshot, telemetry } = usePretable({ + * columns, + * rows, + * viewportHeight: 480, + * }); + * ``` + * + * @public + */ export function usePretable({ autosize, columns, rows, getRowId, -}: UsePretableOptions) { - return useMemo( - () => createGrid({ columns, rows, getRowId, autosize }), - [autosize, columns, getRowId, rows], - ); -} - -export function usePretableModel({ - autosize, - columns, - rows, - getRowId, viewportHeight, viewportWidth, overscan = 6, @@ -108,8 +143,11 @@ export function usePretableModel({ measuredHeights, onSelectionChange, onFocusChange, -}: UsePretableModelOptions): PretableModel { - const grid = usePretable({ autosize, columns, rows, getRowId }); +}: UsePretableOptions): PretableModel { + const grid = useMemo( + () => createGrid({ columns, rows, getRowId, autosize }), + [autosize, columns, getRowId, rows], + ); const lastColumnIdsRef = useRef(null); useLayoutEffect(() => { @@ -154,9 +192,6 @@ export function usePretableModel({ } if (state.columnOrder !== undefined) { - // Apply order by repositioning each column to its position in the - // requested order. Missing ids are appended at the end (engine - // contract). const targetOrder = state.columnOrder; const currentIds = grid.options.columns.map((c) => c.id); const targetIds = [