@jielga/tmdatagrid 1.0.0 → 1.0.2
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 +8 -8
- package/dist/index.d.ts +225 -225
- package/dist/index.js +58 -50
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -2
- package/skills/appearance/SKILL.md +322 -0
- package/skills/cell-selection/SKILL.md +240 -0
- package/skills/columns/SKILL.md +261 -86
- package/skills/data/SKILL.md +289 -0
- package/skills/editing/SKILL.md +492 -0
- package/skills/editing/references/editing-api.md +124 -0
- package/skills/editing/references/editors-and-validation.md +198 -0
- package/skills/filtering/SKILL.md +344 -0
- package/skills/getting-started/SKILL.md +48 -27
- package/skills/grouping/SKILL.md +264 -0
- package/skills/options/SKILL.md +31 -20
- package/skills/rows/SKILL.md +369 -0
- package/skills/rows/references/rows-api.md +117 -0
- package/skills/server-side/SKILL.md +7 -7
- package/skills/testing/SKILL.md +12 -12
- package/src/tmdatagrid/TMDataGridContext.ts +2 -2
- package/src/tmdatagrid/components/TMDataGrid.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGrid.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +12 -6
- package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +8 -8
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +12 -12
- package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +8 -8
- package/src/tmdatagrid/components/TMDataGridTable.module.css +18 -18
- package/src/tmdatagrid/components/TMDataGridTable.tsx +116 -116
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +5 -5
- package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/editorShared.ts +3 -3
- package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +2 -2
- package/src/tmdatagrid/components/sticky.module.css +8 -8
- package/src/tmdatagrid/core/autosize.ts +4 -4
- package/src/tmdatagrid/core/capabilities.ts +17 -17
- package/src/tmdatagrid/core/cellExport.ts +13 -13
- package/src/tmdatagrid/core/cellNavigation.ts +6 -6
- package/src/tmdatagrid/core/cellRange.ts +4 -4
- package/src/tmdatagrid/core/columnOptions.ts +2 -2
- package/src/tmdatagrid/core/columnOrdering.ts +2 -2
- package/src/tmdatagrid/core/columnUtils.ts +3 -3
- package/src/tmdatagrid/core/editEngine.ts +49 -49
- package/src/tmdatagrid/core/expanding.ts +5 -5
- package/src/tmdatagrid/core/filterControls.ts +5 -5
- package/src/tmdatagrid/core/filterOperators.ts +14 -14
- package/src/tmdatagrid/core/labels.ts +8 -8
- package/src/tmdatagrid/core/matchHighlight.ts +4 -4
- package/src/tmdatagrid/core/persistence.ts +8 -8
- package/src/tmdatagrid/core/quickSearch.ts +8 -8
- package/src/tmdatagrid/core/rowPinning.ts +3 -3
- package/src/tmdatagrid/core/rowSelection.ts +12 -12
- package/src/tmdatagrid/core/sizes.ts +1 -1
- package/src/tmdatagrid/core/summary.ts +3 -3
- package/src/tmdatagrid/useTMDataGrid.tsx +79 -79
- package/skills/features/SKILL.md +0 -352
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data
|
|
3
|
+
description: >
|
|
4
|
+
How a TMDataGrid presents the rows it has: pagination, virtualization, and
|
|
5
|
+
having nothing to show. Covers the three pagination modes (off,
|
|
6
|
+
enablePagination, manualPagination), the Footer's pagination render prop and
|
|
7
|
+
getTMDataGridPaginationApi, isPagingActive versus canPaginate, always-on
|
|
8
|
+
virtualization with overscan and meta.rowHeight, scrollToRow and scrollerRef,
|
|
9
|
+
the edge callbacks onScrollToBottom / onScrollToRight and why onReachEnd is
|
|
10
|
+
better for loading more, the header and pinned-lane depth shadows, and the
|
|
11
|
+
four empty states in precedence order with meta.loading, renderEmptyState,
|
|
12
|
+
hasActiveFilters, TMDataGrid.LoadingIndicator and TMDataGrid.SummaryCount.
|
|
13
|
+
Load when adding a pager, tuning scrolling, scrolling to a row, or deciding
|
|
14
|
+
what an empty grid should say.
|
|
15
|
+
metadata:
|
|
16
|
+
type: core
|
|
17
|
+
library: '@jielga/tmdatagrid'
|
|
18
|
+
library_version: '1.0.2'
|
|
19
|
+
sources:
|
|
20
|
+
- 'Jielga/TMDataGrid:src/docs/pagination.md'
|
|
21
|
+
- 'Jielga/TMDataGrid:src/docs/scrolling.md'
|
|
22
|
+
- 'Jielga/TMDataGrid:src/docs/loading-and-empty.md'
|
|
23
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGridFooter.tsx'
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# TMDataGrid - Pagination, scrolling and empty states
|
|
27
|
+
|
|
28
|
+
## Pagination
|
|
29
|
+
|
|
30
|
+
**Off by default.** The grid renders every filtered and sorted row and relies on
|
|
31
|
+
virtualization, which handles any row count - so paging is a choice about how
|
|
32
|
+
the reader navigates, not a performance workaround. Three modes:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// 1. None (default). TMDataGrid.Footer renders nothing.
|
|
36
|
+
const grid = useTMDataGrid({ data, columns });
|
|
37
|
+
|
|
38
|
+
// 2. Client. The table pages, the Footer renders its pager.
|
|
39
|
+
const grid = useTMDataGrid({ data, columns, enablePagination: true });
|
|
40
|
+
|
|
41
|
+
// 3. Manual. The server pages; manualPagination implies enablePagination.
|
|
42
|
+
const grid = useTMDataGrid({
|
|
43
|
+
data: page.rows,
|
|
44
|
+
columns,
|
|
45
|
+
manualPagination: true,
|
|
46
|
+
rowCount: page.total,
|
|
47
|
+
state: { pagination },
|
|
48
|
+
onPaginationChange: setPagination,
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Initial page size is 25, through `initialState.pagination`. `enablePagination`
|
|
53
|
+
is one of the two switches the grid defines itself, and the one switch that
|
|
54
|
+
defaults to off.
|
|
55
|
+
|
|
56
|
+
`TMDataGrid.Footer` takes a `pagination` render prop handed the same API the
|
|
57
|
+
built-in pager is built on, and `getTMDataGridPaginationApi(table)` returns that
|
|
58
|
+
object outside the Footer:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<TMDataGrid.Footer
|
|
62
|
+
pagination={(api) => (
|
|
63
|
+
<Group>
|
|
64
|
+
<Button onClick={api.previousPage} disabled={!api.canPreviousPage}>
|
|
65
|
+
Back
|
|
66
|
+
</Button>
|
|
67
|
+
<Text>
|
|
68
|
+
{api.pageIndex + 1} / {api.pageCount}
|
|
69
|
+
</Text>
|
|
70
|
+
<Button onClick={api.nextPage} disabled={!api.canNextPage}>
|
|
71
|
+
Next
|
|
72
|
+
</Button>
|
|
73
|
+
</Group>
|
|
74
|
+
)}
|
|
75
|
+
/>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Grouping suspends the pager and wins: it greys out and the range becomes
|
|
79
|
+
`Grouped · all N rows`. `isPagingActive(table, features)` is the live state -
|
|
80
|
+
whether the pager is slicing anything right now - while
|
|
81
|
+
`getGridCapabilities(...).canPaginate` is the configuration. The two differ
|
|
82
|
+
exactly while a grouping is active.
|
|
83
|
+
|
|
84
|
+
## Scrolling
|
|
85
|
+
|
|
86
|
+
Virtualization is **always on**. There is no flag, no threshold and no "enable
|
|
87
|
+
for large data sets": only the rows within the viewport plus a small overscan
|
|
88
|
+
are ever mounted.
|
|
89
|
+
|
|
90
|
+
`overscan` (default `6`) is the one knob - raise it if a fast scroll flashes
|
|
91
|
+
blank rows, lower it when rows are expensive. Row height comes from
|
|
92
|
+
`meta.rowHeight`, or from `size` when that is not set, and rows are **fixed
|
|
93
|
+
height**, so the virtualizer's estimate is exact and the scrollbar is honest.
|
|
94
|
+
Row details are the exception: a row showing a panel is measured after it
|
|
95
|
+
mounts.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const grid = useTMDataGrid({
|
|
99
|
+
data,
|
|
100
|
+
columns,
|
|
101
|
+
getRowId: (row) => String(row.id),
|
|
102
|
+
overscan: 12,
|
|
103
|
+
meta: { rowHeight: 64 },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
grid.scrollToRow({ rowId: "42", align: "center" });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`scrollToRow` exists because the row may not be mounted, which is exactly what
|
|
110
|
+
`element.scrollIntoView()` cannot handle. `align` is `"start"`, `"center"`,
|
|
111
|
+
`"end"` or `"auto"`. `scrollerRef` is the scroll container itself.
|
|
112
|
+
|
|
113
|
+
`TMDataGrid.Table` reports arrivals at each edge - `onScrollToTop`,
|
|
114
|
+
`onScrollToBottom`, `onScrollToLeft`, `onScrollToRight` - firing **once** on
|
|
115
|
+
arrival rather than on every scroll event. For loading more rows prefer
|
|
116
|
+
`onReachEnd` (the `server-side` skill): it fires rows early rather than at the
|
|
117
|
+
very bottom, and latches per row count so a pending fetch is never asked twice.
|
|
118
|
+
|
|
119
|
+
Two depth cues are scroll-driven animations on the compositor, with no scroll
|
|
120
|
+
listener and no React render: a shadow under the sticky header once rows scroll
|
|
121
|
+
beneath it, and a band beside a pinned lane while it is actually covering
|
|
122
|
+
something.
|
|
123
|
+
|
|
124
|
+
## Empty states
|
|
125
|
+
|
|
126
|
+
There are four ways to have nothing to show, and an empty body shows exactly one
|
|
127
|
+
of them, in this order:
|
|
128
|
+
|
|
129
|
+
1. **Loading** - `meta.loading` is true: a centred loader. A grid that is
|
|
130
|
+
fetching never claims to be empty.
|
|
131
|
+
2. **Entry rows** - an open entry row from `edit.addRow()`: only the entry
|
|
132
|
+
block, with no message competing with the form.
|
|
133
|
+
3. **`renderEmptyState`** - your node, centred where the message would be.
|
|
134
|
+
4. **Filtered-empty** - a filter or search is active: `labels.noResults`,
|
|
135
|
+
because this emptiness is the reader's own doing.
|
|
136
|
+
5. **Truly-empty** - no data at all: `labels.noRows`.
|
|
137
|
+
|
|
138
|
+
`renderEmptyState` replaces the last two with one render prop, and
|
|
139
|
+
`hasActiveFilters` says which it is standing in for:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<TMDataGrid.Table<Employee>
|
|
143
|
+
renderEmptyState={({ hasActiveFilters, table }) =>
|
|
144
|
+
hasActiveFilters ? (
|
|
145
|
+
<Button variant="light" onClick={() => table.resetColumnFilters()}>
|
|
146
|
+
Clear filters
|
|
147
|
+
</Button>
|
|
148
|
+
) : (
|
|
149
|
+
<Button onClick={openCreateModal}>Add the first employee</Button>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
An empty grid is where a reader is most likely to be stuck, so it is worth
|
|
156
|
+
giving them the action that unsticks them.
|
|
157
|
+
|
|
158
|
+
The body's loading state only appears while the grid is **empty**. A
|
|
159
|
+
server-driven grid refetching with rows still on screen keeps showing them;
|
|
160
|
+
`TMDataGrid.LoadingIndicator` is the signal for that case, a small spinner while
|
|
161
|
+
`meta.loading` is true. `TMDataGrid.SummaryCount` shows visible rows out of
|
|
162
|
+
total, where the total is `meta.totalRowCount` when provided and the pre-filtered
|
|
163
|
+
count otherwise.
|
|
164
|
+
|
|
165
|
+
## Common mistakes
|
|
166
|
+
|
|
167
|
+
### CRITICAL Turning pagination on to make a large grid fast
|
|
168
|
+
|
|
169
|
+
Virtualization is already unconditional, so paging a 200 000-row grid changes
|
|
170
|
+
nothing about rendering cost - it only takes navigation away from the reader.
|
|
171
|
+
Reach for it when the reader should move page by page, not for performance.
|
|
172
|
+
|
|
173
|
+
Source: `src/docs/pagination.md`, `src/docs/scrolling.md`.
|
|
174
|
+
|
|
175
|
+
### CRITICAL A variable row height
|
|
176
|
+
|
|
177
|
+
The virtualizer needs one number, and rows are fixed height so the scrollbar
|
|
178
|
+
stays honest. Styling a taller row through CSS leaves the measurement and the
|
|
179
|
+
render disagreeing: rows overlap or gaps open as you scroll, and the effect
|
|
180
|
+
depends on scroll position, so it looks intermittent.
|
|
181
|
+
|
|
182
|
+
Wrong:
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
[data-dg-part="row"] { height: 64px; }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Correct:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
useTMDataGrid({ data, columns, meta: { rowHeight: 64 } });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Source: `src/docs/scrolling.md` (Row height).
|
|
195
|
+
|
|
196
|
+
### HIGH `scrollIntoView` on a row that is not mounted
|
|
197
|
+
|
|
198
|
+
Only the viewport's rows exist in the DOM, so a query for row 4 000 returns
|
|
199
|
+
nothing and the call silently does nothing.
|
|
200
|
+
|
|
201
|
+
Correct:
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
grid.scrollToRow({ rowId: "4000", align: "center" });
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`scrollToRow` returns `false` when the row is not in the current view - filtered
|
|
208
|
+
out, on another page, or an id matching no row - and nothing scrolled.
|
|
209
|
+
|
|
210
|
+
Source: `src/docs/scrolling.md` (Scrolling to a row).
|
|
211
|
+
|
|
212
|
+
### HIGH Loading more rows from `onScrollToBottom`
|
|
213
|
+
|
|
214
|
+
It fires at the very bottom, so the reader waits at the end of the list for the
|
|
215
|
+
fetch. `onReachEnd` fires rows early and latches per row count, so a pending
|
|
216
|
+
fetch is never asked twice.
|
|
217
|
+
|
|
218
|
+
Source: `src/docs/scrolling.md` (Edge callbacks).
|
|
219
|
+
|
|
220
|
+
### HIGH `SummaryCount` reporting the page under manual pagination
|
|
221
|
+
|
|
222
|
+
The client only holds one page, so without `meta.totalRowCount` the total is
|
|
223
|
+
whatever arrived. It shows "25 of 25" over a table of thousands.
|
|
224
|
+
|
|
225
|
+
Correct:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
useTMDataGrid({
|
|
229
|
+
data: page.rows,
|
|
230
|
+
columns,
|
|
231
|
+
manualPagination: true,
|
|
232
|
+
rowCount: page.total,
|
|
233
|
+
meta: { totalRowCount: page.total },
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Source: `src/docs/loading-and-empty.md` (Counting what is there).
|
|
238
|
+
|
|
239
|
+
### MEDIUM Rendering an empty message while data is loading
|
|
240
|
+
|
|
241
|
+
The order exists so a fetching grid never claims to be empty. A hand-rolled
|
|
242
|
+
`data.length === 0 ? <Empty /> : <Grid />` outside the grid flashes "No rows to
|
|
243
|
+
show" on every load.
|
|
244
|
+
|
|
245
|
+
Correct:
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
useTMDataGrid({ data, columns, meta: { loading: isFetching } });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Source: `src/docs/loading-and-empty.md` (What wins).
|
|
252
|
+
|
|
253
|
+
### MEDIUM Trusting the pager while grouped
|
|
254
|
+
|
|
255
|
+
`getPageCount()` still returns a number, but the pager is greyed out and the
|
|
256
|
+
grid is rendering the whole tree. A custom pager must read
|
|
257
|
+
`isPagingActive(table, features)` rather than the page count.
|
|
258
|
+
|
|
259
|
+
Source: `src/docs/pagination.md` (Grouping suspends it).
|
|
260
|
+
|
|
261
|
+
## Reference
|
|
262
|
+
|
|
263
|
+
| Name | Kind | Type | Default | What it does |
|
|
264
|
+
| --- | --- | --- | --- | --- |
|
|
265
|
+
| `enablePagination` | Option | `boolean` | `false` | Client-side paging and the Footer's pager. Grid-defined. |
|
|
266
|
+
| `manualPagination` | Table option | `boolean` | `false` | The server pages. Implies `enablePagination`. |
|
|
267
|
+
| `rowCount` | Table option | `number` | – | The true total, required under `manualPagination`. |
|
|
268
|
+
| `initialState.pagination` | Table option | `{ pageIndex, pageSize }` | `{ 0, 25 }` | Where paging starts. A `data` slice, so it persists. |
|
|
269
|
+
| `onPaginationChange` | Table option | `OnChangeFn` | – | Controls the pagination state. |
|
|
270
|
+
| `TMDataGrid.Footer` | Component | `pageSizeOptions`, `pagination` | `[10, 25, 50, 100]` | The footer bar. Renders nothing when paging is off. |
|
|
271
|
+
| `getTMDataGridPaginationApi` | Export | `(table) => TMDataGridPaginationApi` | – | The pager API, outside the Footer. |
|
|
272
|
+
| `isPagingActive` | Export | `(table, features) => boolean` | – | Whether the pager is slicing anything right now. |
|
|
273
|
+
| `overscan` | Option | `number` | `6` | Rows kept mounted beyond each edge of the viewport. |
|
|
274
|
+
| `meta.rowHeight` | Option | `number` | From `size` | Row height in pixels. The virtualizer needs a number. |
|
|
275
|
+
| `scrollToRow` | Hook return | `({ rowId, align? }) => boolean` | `align: "auto"` | Scrolls a row into view, mounted or not. |
|
|
276
|
+
| `scrollerRef` | Hook return | `RefObject<HTMLDivElement>` | – | The scroll container. |
|
|
277
|
+
| `onScrollToTop` · `onScrollToBottom` · `onScrollToLeft` · `onScrollToRight` | Table props | `() => void` | – | Fire once on arriving at that edge. |
|
|
278
|
+
| `TMDataGridScrollAlign` | Export | `"start" \| "center" \| "end" \| "auto"` | – | The `align` argument. |
|
|
279
|
+
| `meta.loading` | Option | `boolean` | `false` | A fetch is in flight. Outranks every empty message. |
|
|
280
|
+
| `meta.noResultsLabel` | Option | `string` | `labels.noResults` | The filtered-empty message, without a render prop. |
|
|
281
|
+
| `meta.totalRowCount` | Option | `number` | Pre-filtered count | The total `SummaryCount` reports. |
|
|
282
|
+
| `renderEmptyState` | Table prop | `({ hasActiveFilters, table }) => ReactNode` | – | Replaces both built-in empty messages. |
|
|
283
|
+
| `TMDataGrid.LoadingIndicator` | Component | – | – | Spinner while `meta.loading`, for when the body has rows. |
|
|
284
|
+
| `TMDataGrid.SummaryCount` | Component | `children` replaces the text | – | Visible rows out of total. |
|
|
285
|
+
| `--dg-header-shadow-color` | CSS variable | colour | Themed | The shadow under the sticky header. |
|
|
286
|
+
| `--dg-sticky-edge-range` | CSS variable | length | `20px` | How far the pinned-lane band takes to fade in. |
|
|
287
|
+
|
|
288
|
+
See also: the `server-side` skill for `manualPagination` and `onReachEnd`, and
|
|
289
|
+
the `grouping` skill for why the pager suspends.
|