react-glide-table 1.1.9 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,24 +18,24 @@ Peer dependencies: `react` and `react-dom` (`^18` or `^19`).
18
18
 
19
19
  The package is marked `"sideEffects": false` for tree-shaking. Prefer subpath imports when you only need one surface:
20
20
 
21
- | Import | Contents |
22
- | --- | --- |
23
- | `react-glide-table` | Full barrel (compound + core) |
24
- | `react-glide-table/compound` | `createTable` / `Table` / `DataTable` + related types |
25
- | `react-glide-table/core` | `useGlideTable` + feature hooks/helpers (no compound UI) |
21
+ | Import | Contents |
22
+ | ---------------------------- | -------------------------------------------------------- |
23
+ | `react-glide-table` | Full barrel (compound + core) |
24
+ | `react-glide-table/compound` | `createTable` / `Table` / `DataTable` + related types |
25
+ | `react-glide-table/core` | `useGlideTable` + feature hooks/helpers (no compound UI) |
26
26
 
27
27
  ## Quick start (compound)
28
28
 
29
29
  ```tsx
30
- import { createTable } from "react-glide-table/compound"
31
- import { useState } from "react"
30
+ import { createTable } from "react-glide-table/compound";
31
+ import { useState } from "react";
32
32
 
33
- type Product = { id: string; name: string; qty: number }
33
+ type Product = { id: string; name: string; qty: number };
34
34
 
35
- const ProductTable = createTable<Product>()
35
+ const ProductTable = createTable<Product>();
36
36
 
37
37
  export function Products({ data }: { data: Product[] }) {
38
- const [page, setPage] = useState(1)
38
+ const [page, setPage] = useState(1);
39
39
 
40
40
  return (
41
41
  <ProductTable
@@ -59,26 +59,26 @@ export function Products({ data }: { data: Product[] }) {
59
59
  </ProductTable.Header>
60
60
  <ProductTable.Pagination page={page} pageSize={10} onChange={setPage} />
61
61
  </ProductTable>
62
- )
62
+ );
63
63
  }
64
64
  ```
65
65
 
66
66
  ### Customization surface
67
67
 
68
- | Slot / prop | Role |
69
- | --- | --- |
70
- | `classNames` | Per-part Tailwind/utility classes (`root`, `scroll`, `row`, `cell`, `toolbar`, …) |
71
- | `slots.Toolbar` | Top summary / actions region |
72
- | `slots.Row` | Full row replacement (cells, selection, edit UI) |
73
- | `slots.Pending` / `slots.Empty` | Loading and empty states |
74
- | `className` / column `className` / `headerClassName` | Extra class hooks |
75
- | `labels` / `summary` / `toolbar` | Copy and slot nodes |
76
- | `Column.render` | Cell content custom render |
68
+ | Slot / prop | Role |
69
+ | ---------------------------------------------------- | --------------------------------------------------------------------------------- |
70
+ | `classNames` | Per-part Tailwind/utility classes (`root`, `scroll`, `row`, `cell`, `toolbar`, …) |
71
+ | `slots.Toolbar` | Top summary / actions region |
72
+ | `slots.Row` | Full row replacement (cells, selection, edit UI) |
73
+ | `slots.Pending` / `slots.Empty` | Loading and empty states |
74
+ | `className` / column `className` / `headerClassName` | Extra class hooks |
75
+ | `labels` / `summary` / `toolbar` | Copy and slot nodes |
76
+ | `Column.render` | Cell content custom render |
77
77
 
78
78
  Row/cell **state** is exposed as `data-*` attributes for Tailwind variants:
79
79
 
80
80
  - row: `data-selected`, `data-hovered`, `data-expandable`, `data-expanded`
81
- - cell: `data-merged`, `data-selection-fill`, `data-editable`, `data-editing`, …
81
+ - cell: `data-merged`, `data-selection-fill`, `data-editable`, `data-editing`, `data-frozen`,
82
82
 
83
83
  Example: `row: "data-[selected]:bg-blue-600"`.
84
84
 
@@ -87,16 +87,16 @@ Row-level UI → `slots.Row`. Cell content → `Column.render`. Header/Cell are
87
87
  ## Escape hatch (`useGlideTable`)
88
88
 
89
89
  ```tsx
90
- import { flexRender } from "@tanstack/react-table"
91
- import { useGlideTable } from "react-glide-table/core"
92
- import type { ColumnDef } from "react-glide-table/core"
90
+ import { flexRender } from "@tanstack/react-table";
91
+ import { useGlideTable } from "react-glide-table/core";
92
+ import type { ColumnDef } from "react-glide-table/core";
93
93
 
94
- type Product = { id: string; name: string; qty: number }
94
+ type Product = { id: string; name: string; qty: number };
95
95
 
96
96
  const columns: ColumnDef<Product, unknown>[] = [
97
97
  { accessorKey: "name", header: "Name" },
98
98
  { accessorKey: "qty", header: "Qty" },
99
- ]
99
+ ];
100
100
 
101
101
  export function ProductTable({ data }: { data: Product[] }) {
102
102
  const { table, rows, scrollRef } = useGlideTable({
@@ -104,7 +104,7 @@ export function ProductTable({ data }: { data: Product[] }) {
104
104
  columns,
105
105
  getRowId: (row) => row.id,
106
106
  rowSelectionMode: "multi",
107
- })
107
+ });
108
108
 
109
109
  return (
110
110
  <div ref={scrollRef}>
@@ -116,7 +116,10 @@ export function ProductTable({ data }: { data: Product[] }) {
116
116
  <th key={header.id}>
117
117
  {header.isPlaceholder
118
118
  ? null
119
- : flexRender(header.column.columnDef.header, header.getContext())}
119
+ : flexRender(
120
+ header.column.columnDef.header,
121
+ header.getContext(),
122
+ )}
120
123
  </th>
121
124
  ))}
122
125
  </tr>
@@ -135,7 +138,7 @@ export function ProductTable({ data }: { data: Product[] }) {
135
138
  </tbody>
136
139
  </table>
137
140
  </div>
138
- )
141
+ );
139
142
  }
140
143
  ```
141
144
 
@@ -145,17 +148,17 @@ Wire `rowContextValue` into your own row/cell components for edit, selection, ex
145
148
 
146
149
  Cell selection ships with clipboard shortcuts. The table parses TSV and emits structured payloads; **your app applies domain conversion and updates data**.
147
150
 
148
- | Shortcut | Behavior |
149
- | --- | --- |
150
- | Ctrl/Cmd+C | Copy the active selection (visible rows) |
151
- | Ctrl/Cmd+Shift+C | Copy including collapsed tree descendants (`enableSubtreeCopy`) |
152
- | Ctrl/Cmd+V | Paste **overwrite** into the selection (`onRowsPaste`, `mode: "overwrite"`) |
151
+ | Shortcut | Behavior |
152
+ | ---------------- | -------------------------------------------------------------------------------------- |
153
+ | Ctrl/Cmd+C | Copy the active selection (visible rows) |
154
+ | Ctrl/Cmd+Shift+C | Copy including collapsed tree descendants (`enableSubtreeCopy`) |
155
+ | Ctrl/Cmd+V | Paste **overwrite** into the selection (`onRowsPaste`, `mode: "overwrite"`) |
153
156
  | Ctrl/Cmd+Shift+V | Paste **insert** rows after the selection (`mode: "insert"`, when `enableInsertPaste`) |
154
157
 
155
158
  Subtree copy encodes relative tree depth as leading tabs in the TSV so paste can rebuild parent/child nesting via `payload.depths`. Depth is only inferred when the clipboard looks like subtree indentation (first row unindented, at least one later row indented). Otherwise leading empty cells are kept as real values (e.g. Excel/Sheets blank first column) and `depths` stay `0`.
156
159
 
157
160
  ```tsx
158
- import type { RowsPastePayload } from "react-glide-table/compound"
161
+ import type { RowsPastePayload } from "react-glide-table/compound";
159
162
 
160
163
  <ProductTable
161
164
  data={data}
@@ -164,27 +167,91 @@ import type { RowsPastePayload } from "react-glide-table/compound"
164
167
  onRowsPaste={(payload: RowsPastePayload) => {
165
168
  // overwrite: update cells from startRow using payload.values / columnIds
166
169
  // insert: create rows after payload.endRow (or payload.anchorRowId for trees)
167
- setData((prev) => applyMyPaste(prev, payload))
170
+ setData((prev) => applyMyPaste(prev, payload));
168
171
  }}
169
172
  >
170
173
  {/* columns… */}
171
- </ProductTable>
174
+ </ProductTable>;
172
175
  ```
173
176
 
174
177
  Related props: `onRowsPaste`, `enableInsertPaste`, `enableSubtreeCopy`, `onCopyActionsReady`.
175
178
  Helpers (`/core`): `buildRowsPastePayload`, `parseClipboardTSV`, `parseClipboardTSVWithDepths`, `serializeSelectionToTSV`, …
176
179
 
180
+ ## Column resize
181
+
182
+ Opt in with `enableColumnResize`. Drag the handle on the right edge of a header cell; double-click resets to the column’s default `width` / `size`.
183
+
184
+ ```tsx
185
+ <ProductTable
186
+ data={data}
187
+ enableColumnResize
188
+ // optional controlled sizing
189
+ // columnSizing={sizing}
190
+ // onColumnSizingChange={setSizing}
191
+ >
192
+ <ProductTable.Header>
193
+ <ProductTable.Column field="name" width={200} minWidth={80} maxWidth={480}>
194
+ Name
195
+ </ProductTable.Column>
196
+ <ProductTable.Column field="sku" resizable={false}>
197
+ SKU
198
+ </ProductTable.Column>
199
+ </ProductTable.Header>
200
+ </ProductTable>
201
+ ```
202
+
203
+ | Prop | Role |
204
+ | ---------------------------------------- | -------------------------------------------- |
205
+ | `enableColumnResize` | Turn on header drag resize (default `false`) |
206
+ | `columnSizing` / `onColumnSizingChange` | Controlled width map `{ [columnId]: px }` |
207
+ | `columnResizeMode` | `"onChange"` (live) or `"onEnd"` |
208
+ | `Column.width` / `minWidth` / `maxWidth` | Default / clamp sizes |
209
+ | `Column.resizable={false}` | Disable resize for one column |
210
+ | `classNames.resizeHandle` | Style hook for the drag handle |
211
+
212
+ ## Column freeze
213
+
214
+ Opt in with `enableColumnFreeze`. Mark columns with `frozen` — sticky insets are stacked so frozen cells never overlap, and **column order is unchanged** (middle columns may also freeze).
215
+
216
+ ```tsx
217
+ <ProductTable data={data} enableColumnFreeze>
218
+ <ProductTable.Header>
219
+ <ProductTable.Column field="name" frozen width={200}>
220
+ Name
221
+ </ProductTable.Column>
222
+ <ProductTable.Column field="sku">SKU</ProductTable.Column>
223
+ <ProductTable.Column field="qty" frozen="left">
224
+ Qty
225
+ </ProductTable.Column>
226
+ <ProductTable.Column field="status" frozen="right">
227
+ Status
228
+ </ProductTable.Column>
229
+ </ProductTable.Header>
230
+ </ProductTable>
231
+ ```
232
+
233
+ | Prop | Role |
234
+ | ---------------------------------- | --------------------------------------- |
235
+ | `enableColumnFreeze` | Turn on sticky freeze (default `false`) |
236
+ | `Column.frozen` / `meta.frozen` | `true` / `"left"` or `"right"` |
237
+ | `data-frozen` | `"left"` / `"right"` on frozen cells |
238
+ | `data-freeze-edge` | `"left"` / `"right"` / `"both"` on island boundaries |
239
+
240
+ Helpers (`/core`): `buildColumnFreezeOffsets`, `getColumnFreezeEdgeAttr`, `getColumnFreezeStyle`, `resolveColumnFreezeSide`.
241
+
242
+ Contiguous same-side freezes share one island (shadow only on the outer boundary). A gap between frozen columns creates separate islands, so both sides of the gap get an edge shadow.
243
+
177
244
  ## Public API
178
245
 
179
- | Export | Path | Role |
180
- | --- | --- | --- |
181
- | `createTable` / `Table` | `/compound` | Compound column DSL (`Header` / `Column` / `Body` / `Pagination`) |
182
- | `DataTable` | `/compound` | Unstyled default renderer (semantic HTML + slots/props) |
183
- | `useGlideTable` | `/core` | Headless engine escape hatch |
184
- | `useCellEdit` / `useCellSelection` / `useConvertTreeData` | `/core` | Feature hooks |
185
- | `applyCellEdit`, `applyFillData`, `buildRowsPastePayload`, `buildColumnRowSpanMap`, … | `/core` | Pure helpers |
186
- | `DEFAULT_DATA_TABLE_LABELS` / `resolveDataTableLabels` | `/core` | Optional English UI copy helpers |
187
- | Tree field defaults | `/core` | `id` / `parentId` / `children` / `qty` |
246
+ | Export | Path | Role |
247
+ | ------------------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------- |
248
+ | `createTable` / `Table` | `/compound` | Compound column DSL (`Header` / `Column` / `Body` / `Pagination`) |
249
+ | `DataTable` | `/compound` | Unstyled default renderer (semantic HTML + slots/props) |
250
+ | `useGlideTable` | `/core` | Headless engine escape hatch |
251
+ | `useCellEdit` / `useCellSelection` / `useConvertTreeData` | `/core` | Feature hooks |
252
+ | `applyCellEdit`, `applyFillData`, `buildRowsPastePayload`, `buildColumnRowSpanMap`, … | `/core` | Pure helpers |
253
+ | `DEFAULT_DATA_TABLE_LABELS` / `resolveDataTableLabels` | `/core` | Optional English UI copy helpers |
254
+ | Tree field defaults | `/core` | `id` / `parentId` / `children` / `qty` |
188
255
 
189
256
  Root `react-glide-table` re-exports both surfaces. Related types: `TableProps`, `TableColumnProps`, `DataTableProps`, `DataTableSlots`, `TableCompoundComponent`, `ColumnDef`, `RowsPastePayload`, `PasteMode`, …
190
257
 
@@ -192,7 +259,7 @@ Root `react-glide-table` re-exports both surfaces. Related types: `TableProps`,
192
259
 
193
260
  - **Row span + virtualization**: when `enableRowSpan` is on, virtualization is forced off (HTML `<table>` + `rowspan` cannot safely share a virtual window).
194
261
  - **Paste is app-owned**: the library does not mutate `data` on paste — handle `onRowsPaste` (coerce types, ids, tree shape, row-span keys).
195
- - **Flat tree parent order**: `useConvertTreeData` attaches each child to the nearest *preceding* row whose toggle key matches `parentId` (duplicate keys after paste resolve this way). Flat inputs must list parents before their children; a child whose parent appears later becomes a root. Nested `children` arrays are flattened parent-before-child automatically.
262
+ - **Flat tree parent order**: `useConvertTreeData` attaches each child to the nearest _preceding_ row whose toggle key matches `parentId` (duplicate keys after paste resolve this way). Flat inputs must list parents before their children; a child whose parent appears later becomes a root. Nested `children` arrays are flattened parent-before-child automatically.
196
263
  - **No shipped CSS**: the default renderer emits class hooks only. Bring your own styles (see playground for a CSS-skinned example).
197
264
 
198
265
  ## Local playground