@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
|
@@ -70,7 +70,7 @@ export type TMDataGridColumnLayout = {
|
|
|
70
70
|
pinnedAt: "left" | "right" | false;
|
|
71
71
|
/** Distance in px from the grid's left or right edge. */
|
|
72
72
|
offset: number;
|
|
73
|
-
/** Last left-pinned / first right-pinned column
|
|
73
|
+
/** Last left-pinned / first right-pinned column - the one that casts the edge. */
|
|
74
74
|
isBoundary: boolean;
|
|
75
75
|
};
|
|
76
76
|
|
|
@@ -96,7 +96,7 @@ const FOCUSABLE_IN_CELL = [
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* The mounted element for a cell position, or `null` when its row is scrolled
|
|
99
|
-
* out
|
|
99
|
+
* out - the normal case for a move that has to scroll before it can focus.
|
|
100
100
|
*/
|
|
101
101
|
function findCellElement(
|
|
102
102
|
container: HTMLElement,
|
|
@@ -115,7 +115,7 @@ function findCellElement(
|
|
|
115
115
|
* so as far as the browser is concerned a cell sliding underneath them is
|
|
116
116
|
* still perfectly visible, and it stops scrolling the moment the cell's edge
|
|
117
117
|
* reaches the container's. The visible edge is the inside of the pinned lane,
|
|
118
|
-
* which only this component knows
|
|
118
|
+
* which only this component knows - it laid the lanes out.
|
|
119
119
|
*/
|
|
120
120
|
function keepColumnInView({
|
|
121
121
|
container,
|
|
@@ -149,14 +149,14 @@ function keepColumnInView({
|
|
|
149
149
|
* | Cell | Renders |
|
|
150
150
|
* | ---- | ------- |
|
|
151
151
|
* | On a data row | the column's `cell`, as always |
|
|
152
|
-
* | Placeholder
|
|
152
|
+
* | Placeholder - a grouped column other than this row's own | nothing |
|
|
153
153
|
* | Aggregated, column declares `aggregatedCell` | that |
|
|
154
154
|
* | Aggregated, column declares an `aggregationFn` | the column's `cell`, over the aggregate |
|
|
155
155
|
* | Aggregated, column declares neither | nothing |
|
|
156
156
|
*
|
|
157
157
|
* That last row is the one worth spelling out. Without an aggregation function
|
|
158
158
|
* `getValue()` is `undefined` on a group row, so the column's own renderer
|
|
159
|
-
* would be handed nothing
|
|
159
|
+
* would be handed nothing - and a renderer that formats what it gets
|
|
160
160
|
* (`value.toFixed(2)`) would throw. Blank is both the safe answer and the right
|
|
161
161
|
* one: a plain "group by" is a tree, not a summary, and a column only joins in
|
|
162
162
|
* once it has been told how.
|
|
@@ -178,7 +178,7 @@ function renderCellContent(
|
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
180
|
* What a cell needs to take part in cell selection, or `undefined` while the
|
|
181
|
-
* feature is off
|
|
181
|
+
* feature is off - which is what keeps a plain grid's cells exactly as they
|
|
182
182
|
* were: no tab stop, no `gridcell` role, no coordinates in the DOM.
|
|
183
183
|
*
|
|
184
184
|
* The coordinates are there for the delegated key handler, which reads them off
|
|
@@ -196,13 +196,13 @@ export type TMDataGridCellNav = {
|
|
|
196
196
|
selected: boolean;
|
|
197
197
|
/**
|
|
198
198
|
* Which sides of the rectangle this cell is on, or `null` when it is not in
|
|
199
|
-
* one. The outline is drawn per cell
|
|
199
|
+
* one. The outline is drawn per cell - see {@link boundsEdges}.
|
|
200
200
|
*/
|
|
201
201
|
edges: { top: boolean; bottom: boolean; left: boolean; right: boolean } | null;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
* The text with its matched slices wrapped in `<mark>`
|
|
205
|
+
* The text with its matched slices wrapped in `<mark>` - or handed back
|
|
206
206
|
* whole when nothing matches. Only ever called on string output; a renderer
|
|
207
207
|
* returning its own elements never reaches this.
|
|
208
208
|
*/
|
|
@@ -249,12 +249,12 @@ function TMDataGridBodyCell({
|
|
|
249
249
|
/** The mounted editor, when this is the cell being edited. */
|
|
250
250
|
editor?: ReactNode;
|
|
251
251
|
/**
|
|
252
|
-
* Replaces the column renderer's output
|
|
252
|
+
* Replaces the column renderer's output - the row-number gutter, whose
|
|
253
253
|
* value only the body knows. `undefined` means "render normally".
|
|
254
254
|
*/
|
|
255
255
|
contentOverride?: ReactNode;
|
|
256
256
|
/**
|
|
257
|
-
* Needles to highlight in string output
|
|
257
|
+
* Needles to highlight in string output - see `enableMatchHighlighting`.
|
|
258
258
|
* `undefined` almost always: only set while a highlightable filter is
|
|
259
259
|
* active for this column.
|
|
260
260
|
*/
|
|
@@ -267,7 +267,7 @@ function TMDataGridBodyCell({
|
|
|
267
267
|
onContextMenu?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
268
268
|
}) {
|
|
269
269
|
// The edit markers subscribe here, per cell, all to the one projection
|
|
270
|
-
// store
|
|
270
|
+
// store - so typing in an editor repaints the edited row's cells and
|
|
271
271
|
// nothing else. Constant `false` everywhere while editing is off.
|
|
272
272
|
const { table, edit } = useTMDataGridContext();
|
|
273
273
|
const fieldName = getEditFieldName(cell.column);
|
|
@@ -296,7 +296,7 @@ function TMDataGridBodyCell({
|
|
|
296
296
|
data-cell={nav ? true : undefined}
|
|
297
297
|
// Both coordinates are always present, not only under cell selection:
|
|
298
298
|
// autosize finds a column's mounted cells by the column id, and the pair
|
|
299
|
-
// is how a test addresses one cell
|
|
299
|
+
// is how a test addresses one cell -
|
|
300
300
|
// `[data-row-id="42"][data-column-id="name"]`. The cell-navigation
|
|
301
301
|
// selectors also require `data-cell`, so they are unaffected.
|
|
302
302
|
data-row-id={cell.row.id}
|
|
@@ -312,7 +312,7 @@ function TMDataGridBodyCell({
|
|
|
312
312
|
data-edge-left={nav?.edges?.left}
|
|
313
313
|
data-edge-right={nav?.edges?.right}
|
|
314
314
|
// Bubbles, so focus landing on a control inside the cell counts as
|
|
315
|
-
// landing on the cell
|
|
315
|
+
// landing on the cell - the ring follows the user into the checkbox
|
|
316
316
|
// rather than being left behind on whichever cell they came from.
|
|
317
317
|
onFocus={onFocus}
|
|
318
318
|
onClick={onClick}
|
|
@@ -341,7 +341,7 @@ function TMDataGridBodyCell({
|
|
|
341
341
|
left: layout.pinnedAt === "left" ? layout.offset : undefined,
|
|
342
342
|
right: layout.pinnedAt === "right" ? layout.offset : undefined,
|
|
343
343
|
// The focus ring is drawn inside the cell's own box, so the cell has to
|
|
344
|
-
// be able to stack above its neighbours
|
|
344
|
+
// be able to stack above its neighbours - a plain one to lift the ring
|
|
345
345
|
// over the pinned lane it sits beside, a pinned one to keep the ring
|
|
346
346
|
// whole while the rest of the row slides under it. Values from the
|
|
347
347
|
// stacking ladder in TMDataGrid.module.css.
|
|
@@ -360,8 +360,8 @@ function TMDataGridBodyCell({
|
|
|
360
360
|
{editor ?? (
|
|
361
361
|
<span className={classes.cellContent}>
|
|
362
362
|
{(() => {
|
|
363
|
-
// Highlighting replicates the *default* renderer
|
|
364
|
-
// `toString`
|
|
363
|
+
// Highlighting replicates the *default* renderer - value, then
|
|
364
|
+
// `toString` - with the matched slices marked. A column with its
|
|
365
365
|
// own `cell` opts out by existing: its renderer's identity is not
|
|
366
366
|
// the default's, and the grid will not rummage inside its output.
|
|
367
367
|
if (
|
|
@@ -389,7 +389,7 @@ function TMDataGridBodyCell({
|
|
|
389
389
|
* when `rowContextMenu` is set.
|
|
390
390
|
*
|
|
391
391
|
* One instance for the whole body rather than one per row. A Mantine `Menu` is
|
|
392
|
-
* a `Popover`, and a closed Popover still runs its hooks on every render
|
|
392
|
+
* a `Popover`, and a closed Popover still runs its hooks on every render - and
|
|
393
393
|
* the body re-renders on every scroll frame, because that is how virtualization
|
|
394
394
|
* works. Wrapping each of ~20 mounted rows measured a fifth onto a body render;
|
|
395
395
|
* wrapping the group is the same behaviour for one instance.
|
|
@@ -418,7 +418,7 @@ function TMDataGridBodyRowGroup({
|
|
|
418
418
|
role="rowgroup"
|
|
419
419
|
style={{ display: "contents" }}
|
|
420
420
|
// Runs before Mantine's own handler, which `Menu.ContextMenu` composes on
|
|
421
|
-
// top of this one
|
|
421
|
+
// top of this one - so the row and cell are known by the time the
|
|
422
422
|
// dropdown is built and opened at the pointer.
|
|
423
423
|
onContextMenu={enabled ? onContextMenu : undefined}
|
|
424
424
|
>
|
|
@@ -453,7 +453,7 @@ export type TMDataGridRowContextMenuArgs<TData extends RowData> = {
|
|
|
453
453
|
row: Row<TMDataGridFeatures, TData>;
|
|
454
454
|
/**
|
|
455
455
|
* The cell under the pointer. `null` only when the right-click landed on the
|
|
456
|
-
* row but on no cell
|
|
456
|
+
* row but on no cell - which a custom cell renderer that stops propagation
|
|
457
457
|
* can cause.
|
|
458
458
|
*/
|
|
459
459
|
cell: Cell<TMDataGridFeatures, TData, unknown> | null;
|
|
@@ -465,7 +465,7 @@ export type TMDataGridRowContextMenuArgs<TData extends RowData> = {
|
|
|
465
465
|
};
|
|
466
466
|
|
|
467
467
|
/**
|
|
468
|
-
* Builds the contents of a row's context menu
|
|
468
|
+
* Builds the contents of a row's context menu - `Menu.Item`, `Menu.Label`,
|
|
469
469
|
* `Menu.Divider`, `Menu.Sub`, or any other node. The grid owns the `Menu` and
|
|
470
470
|
* its `Menu.Dropdown`; this fills the dropdown.
|
|
471
471
|
*/
|
|
@@ -474,7 +474,7 @@ export type TMDataGridRowContextMenu<TData extends RowData> = (
|
|
|
474
474
|
) => ReactNode;
|
|
475
475
|
|
|
476
476
|
/**
|
|
477
|
-
* What the `onCellClick` family receives
|
|
477
|
+
* What the `onCellClick` family receives - the cell with its row and column,
|
|
478
478
|
* in the consumer's own row type, plus the pointer event.
|
|
479
479
|
*/
|
|
480
480
|
export type TMDataGridCellEventArgs<TData extends RowData> = {
|
|
@@ -486,7 +486,7 @@ export type TMDataGridCellEventArgs<TData extends RowData> = {
|
|
|
486
486
|
|
|
487
487
|
/**
|
|
488
488
|
* Inline styles for a row. Widened to accept CSS variables, because setting
|
|
489
|
-
* `--row-bg` is the supported way to colour a row
|
|
489
|
+
* `--row-bg` is the supported way to colour a row - see {@link
|
|
490
490
|
* TMDataGridTableProps.rowStyle}.
|
|
491
491
|
*/
|
|
492
492
|
export type TMDataGridRowStyle =
|
|
@@ -501,8 +501,8 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
501
501
|
onRowClick?: (row: Row<TMDataGridFeatures, TData>) => void;
|
|
502
502
|
/**
|
|
503
503
|
* Called when a body cell is clicked. Composes, never suppresses: whatever
|
|
504
|
-
* the click already does
|
|
505
|
-
*
|
|
504
|
+
* the click already does - selecting, highlighting, moving the cell cursor
|
|
505
|
+
* - still happens. Group rows sit out the cell handlers the way they sit
|
|
506
506
|
* out `onRowClick`, and for the same reason.
|
|
507
507
|
*/
|
|
508
508
|
onCellClick?: (args: TMDataGridCellEventArgs<TData>) => void;
|
|
@@ -515,7 +515,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
515
515
|
*/
|
|
516
516
|
onCellContextMenu?: (args: TMDataGridCellEventArgs<TData>) => void;
|
|
517
517
|
/**
|
|
518
|
-
* Class for a body row
|
|
518
|
+
* Class for a body row - a string, or a function of the row (group rows
|
|
519
519
|
* included; test `row.getIsGrouped()` to skip them). Added after the grid's
|
|
520
520
|
* own row class.
|
|
521
521
|
*/
|
|
@@ -526,7 +526,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
526
526
|
* Inline style for a body row. To colour a row, set `--row-bg` rather than
|
|
527
527
|
* `background`: the row's own background, its sticky pinned cells and the
|
|
528
528
|
* cell-range tint all read that variable, and hover/selection/highlight
|
|
529
|
-
* keep working on top of it
|
|
529
|
+
* keep working on top of it - a raw `background` bypasses all of them.
|
|
530
530
|
*
|
|
531
531
|
* ```tsx
|
|
532
532
|
* rowStyle={(row) =>
|
|
@@ -539,12 +539,12 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
539
539
|
| ((row: Row<TMDataGridFeatures, TData>) => TMDataGridRowStyle | undefined);
|
|
540
540
|
/**
|
|
541
541
|
* Every second row takes `--dg-row-striped-bg`. Computed from the row's
|
|
542
|
-
* position in the view
|
|
542
|
+
* position in the view - sorting and filtering restripe, and the stripes
|
|
543
543
|
* stay put while the virtualizer mounts and unmounts rows.
|
|
544
544
|
*/
|
|
545
545
|
striped?: boolean;
|
|
546
546
|
/**
|
|
547
|
-
* Called when the scroll *arrives* at an edge
|
|
547
|
+
* Called when the scroll *arrives* at an edge - once per arrival, not per
|
|
548
548
|
* scroll event, and not on mount (the grid starts at the top-left edge).
|
|
549
549
|
* For loading more rows as the end approaches, `onReachEnd` is the better
|
|
550
550
|
* hook: it fires rows-early and latches per row count.
|
|
@@ -557,10 +557,10 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
557
557
|
/** As {@link onScrollToTop}, for the right edge. */
|
|
558
558
|
onScrollToRight?: () => void;
|
|
559
559
|
/**
|
|
560
|
-
* Rendered centred in the body when the view is empty
|
|
560
|
+
* Rendered centred in the body when the view is empty - the branded blank
|
|
561
561
|
* slate. It takes over states 3 and 4 of the empty matrix (see the docs):
|
|
562
562
|
* loading still shows the loader, and open entry rows still show only
|
|
563
|
-
* themselves. `hasActiveFilters` says which emptiness this is
|
|
563
|
+
* themselves. `hasActiveFilters` says which emptiness this is - a filter
|
|
564
564
|
* that matched nothing wants a "clear filters" invitation, a grid with no
|
|
565
565
|
* data wants a "create the first one".
|
|
566
566
|
*/
|
|
@@ -570,7 +570,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
570
570
|
}) => ReactNode;
|
|
571
571
|
/**
|
|
572
572
|
* Contents of the menu a right-click on a row opens, at the pointer. Return
|
|
573
|
-
* `null` to leave a row without one
|
|
573
|
+
* `null` to leave a row without one - the browser's own menu stays suppressed
|
|
574
574
|
* either way.
|
|
575
575
|
*
|
|
576
576
|
* ```tsx
|
|
@@ -588,23 +588,23 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
588
588
|
* />
|
|
589
589
|
* ```
|
|
590
590
|
*
|
|
591
|
-
* Called during render, and only for the row whose menu is open
|
|
591
|
+
* Called during render, and only for the row whose menu is open - so it stays
|
|
592
592
|
* a pure function of the row, and cost per row does not matter.
|
|
593
593
|
*/
|
|
594
594
|
rowContextMenu?: TMDataGridRowContextMenu<TData>;
|
|
595
595
|
/**
|
|
596
|
-
* Passed to the Mantine `Menu` the grid wraps the row in
|
|
596
|
+
* Passed to the Mantine `Menu` the grid wraps the row in - `width`, `shadow`,
|
|
597
597
|
* `position`, `transitionProps` and the rest. Its open state is the grid's.
|
|
598
598
|
*/
|
|
599
599
|
rowContextMenuProps?: Omit<MenuProps, "opened" | "onChange" | "children">;
|
|
600
600
|
/**
|
|
601
601
|
* How Ctrl+C and the export item write values, under
|
|
602
|
-
* `cellSelection: "range"`. Defaults to the Nordic Excel conventions
|
|
602
|
+
* `cellSelection: "range"`. Defaults to the Nordic Excel conventions - see
|
|
603
603
|
* {@link TMDataGridCellExportOptions}.
|
|
604
604
|
*/
|
|
605
605
|
cellExport?: TMDataGridCellExportOptions;
|
|
606
606
|
/**
|
|
607
|
-
* Called when the scroll approaches the last row
|
|
607
|
+
* Called when the scroll approaches the last row - the infinite-scroll
|
|
608
608
|
* hook-in. Append the next page to `data` and the virtualizer keeps its
|
|
609
609
|
* position; fires once per row count, so a pending fetch is not asked
|
|
610
610
|
* again until it lands.
|
|
@@ -614,7 +614,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
614
614
|
* ```
|
|
615
615
|
*
|
|
616
616
|
* Sorting and filtering must be server-side (`manual*`) on a grid that
|
|
617
|
-
* loads this way
|
|
617
|
+
* loads this way - the client only ever holds a prefix of the data. Not
|
|
618
618
|
* compatible with `enablePagination`, which slices the same scroll.
|
|
619
619
|
*/
|
|
620
620
|
onReachEnd?: () => void;
|
|
@@ -623,7 +623,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
623
623
|
*/
|
|
624
624
|
reachEndThreshold?: number;
|
|
625
625
|
/**
|
|
626
|
-
* Accessible name for the grid
|
|
626
|
+
* Accessible name for the grid - what a screen reader announces on entry,
|
|
627
627
|
* and what `getByRole("grid", { name })` matches. Worth setting on any page
|
|
628
628
|
* holding more than one grid; without it they are all just "grid".
|
|
629
629
|
*/
|
|
@@ -634,7 +634,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
|
|
|
634
634
|
|
|
635
635
|
/**
|
|
636
636
|
* The scrollable grid surface. Always virtualized: only the rows inside the
|
|
637
|
-
* viewport (plus overscan) are mounted
|
|
637
|
+
* viewport (plus overscan) are mounted - which is what makes the default
|
|
638
638
|
* no-pagination mode viable at any row count. Pagination is opt-in via
|
|
639
639
|
* `enablePagination` (or implied by `manualPagination`).
|
|
640
640
|
*/
|
|
@@ -683,7 +683,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
683
683
|
const focusedCell = useSelector(ui, (state) => state.focusedCell);
|
|
684
684
|
const cellRange = useSelector(ui, (state) => state.cellRange);
|
|
685
685
|
// Which cell has an editor open. The projection (dirty/error markers) is
|
|
686
|
-
// subscribed per cell instead
|
|
686
|
+
// subscribed per cell instead - see TMDataGridBodyCell.
|
|
687
687
|
const editActive = useSelector(edit.store, (state) => state.active);
|
|
688
688
|
const newRowCount = useSelector(edit.store, (state) => state.newRows.length);
|
|
689
689
|
const deletedRowIds = useSelector(edit.store, (state) => state.deletedRowIds);
|
|
@@ -697,14 +697,14 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
697
697
|
// the rows as delivered, so the same branch serves server paging.
|
|
698
698
|
const rows = getDisplayedRows(table, features);
|
|
699
699
|
|
|
700
|
-
// Per-column highlight needles, or `null` while off or nothing is active
|
|
700
|
+
// Per-column highlight needles, or `null` while off or nothing is active -
|
|
701
701
|
// the common case, costing one flag check per render.
|
|
702
702
|
const matchNeedles = features.matchHighlighting
|
|
703
703
|
? buildMatchNeedles(table)
|
|
704
704
|
: null;
|
|
705
705
|
|
|
706
706
|
// The edge blocks. `getDisplayedRows` above has already taken these out of
|
|
707
|
-
// the scrolling order; stale ids resolve to nothing
|
|
707
|
+
// the scrolling order; stale ids resolve to nothing - see readPinnedRows.
|
|
708
708
|
const pinnedTopRows = features.rowPinning ? readPinnedRows(table, "top") : [];
|
|
709
709
|
const pinnedBottomRows = features.rowPinning
|
|
710
710
|
? readPinnedRows(table, "bottom")
|
|
@@ -713,7 +713,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
713
713
|
|
|
714
714
|
// The row-number gutter's numbers: one pass over the view per render, only
|
|
715
715
|
// while the lane exists. A cell cannot know its display position, so the
|
|
716
|
-
// body hands each row its number
|
|
716
|
+
// body hands each row its number - group rows get none (they are headings
|
|
717
717
|
// over the rows being counted), and paging continues the count across
|
|
718
718
|
// pages rather than restarting every page at 1.
|
|
719
719
|
let rowNumberById: Map<string, number> | null = null;
|
|
@@ -732,7 +732,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
732
732
|
* Whether this row opens a detail panel underneath it.
|
|
733
733
|
*
|
|
734
734
|
* Group rows are out: expanding one opens its children, and they share the
|
|
735
|
-
* `expanded` state with details
|
|
735
|
+
* `expanded` state with details - so a grouped grid with `renderDetails` set
|
|
736
736
|
* would otherwise render a panel about the group's first record.
|
|
737
737
|
*/
|
|
738
738
|
const showsDetails = (row: Row<TMDataGridFeatures, TMDataGridRowData>) =>
|
|
@@ -741,7 +741,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
741
741
|
/**
|
|
742
742
|
* `meta.autoSize` columns, sized once after the first rows are in the DOM.
|
|
743
743
|
* A ref rather than an effect dependency: this must run exactly once per
|
|
744
|
-
* mount, and only for columns no persisted or user width already covers
|
|
744
|
+
* mount, and only for columns no persisted or user width already covers -
|
|
745
745
|
* autosizing again on data changes would fight a width the user has since
|
|
746
746
|
* dragged.
|
|
747
747
|
*/
|
|
@@ -760,7 +760,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
760
760
|
|
|
761
761
|
/**
|
|
762
762
|
* The header's height, published as `--dg-header-height` for the entry
|
|
763
|
-
* block to stick under
|
|
763
|
+
* block to stick under - never measured otherwise (`top: 0` needs no
|
|
764
764
|
* number). A ResizeObserver, mounted only while the block is in use: the
|
|
765
765
|
* same discipline `renderDetails` applies to `measureElement`.
|
|
766
766
|
*/
|
|
@@ -785,7 +785,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
785
785
|
|
|
786
786
|
/**
|
|
787
787
|
* The entry block's height, published as `--dg-entry-height` so the pinned
|
|
788
|
-
* top block can stack under it
|
|
788
|
+
* top block can stack under it - measured only while both blocks exist,
|
|
789
789
|
* which is the only time the offset is anything but 0.
|
|
790
790
|
*/
|
|
791
791
|
useEffect(() => {
|
|
@@ -847,14 +847,14 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
847
847
|
getScrollElement: () => scrollContainerRef.current,
|
|
848
848
|
/**
|
|
849
849
|
* Exact for a plain row, which is fixed at `rowHeight`, and a guess for a
|
|
850
|
-
* row showing a detail panel
|
|
850
|
+
* row showing a detail panel - the panel is as tall as whatever the
|
|
851
851
|
* consumer renders. Rows are measured whenever details are on (see
|
|
852
852
|
* `measureElement` below), so this only has to hold until the row has been
|
|
853
853
|
* on screen once.
|
|
854
854
|
*
|
|
855
855
|
* Not a `useCallback`, unlike `getItemKey` below it: the virtualizer's
|
|
856
856
|
* measurement cache lists `getItemKey` among its dependencies and not this,
|
|
857
|
-
* so a stable identity here would buy nothing
|
|
857
|
+
* so a stable identity here would buy nothing - and the closure has to see
|
|
858
858
|
* the current expansion to answer at all.
|
|
859
859
|
*/
|
|
860
860
|
estimateSize: (index: number) => {
|
|
@@ -864,8 +864,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
864
864
|
},
|
|
865
865
|
/**
|
|
866
866
|
* Zero is never a row: every one carries `minHeight: rowHeight`. An element
|
|
867
|
-
* reports it before layout has run
|
|
868
|
-
* layout at all
|
|
867
|
+
* reports it before layout has run - and always under jsdom, which has no
|
|
868
|
+
* layout at all - and a row cached at zero height stays that way until
|
|
869
869
|
* something resizes it. The estimate stands in until there is a real
|
|
870
870
|
* measurement.
|
|
871
871
|
*/
|
|
@@ -892,7 +892,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
892
892
|
*/
|
|
893
893
|
useEffect(() => {
|
|
894
894
|
scrollerRef.current = ({ rowId, align = "auto" }) => {
|
|
895
|
-
// Already parked at an edge, and outside the scrolling order
|
|
895
|
+
// Already parked at an edge, and outside the scrolling order - see
|
|
896
896
|
// `getDisplayedRows`. Nothing to scroll, but the row *is* on screen, so
|
|
897
897
|
// reporting failure would be the wrong answer.
|
|
898
898
|
if (
|
|
@@ -915,7 +915,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
915
915
|
* Infinite scroll. Latched on the row count rather than debounced: one call
|
|
916
916
|
* per count means a pending fetch is never asked twice, and the append that
|
|
917
917
|
* resolves it changes the count, which re-arms the latch. A failed fetch
|
|
918
|
-
* stays quiet until something changes the rows
|
|
918
|
+
* stays quiet until something changes the rows - retrying is the
|
|
919
919
|
* consumer's call, not a scroll side effect.
|
|
920
920
|
*
|
|
921
921
|
* Effect (not render): `onReachEnd` sets consumer state.
|
|
@@ -928,7 +928,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
928
928
|
if (features.pagination && !warnedReachEndPaginationRef.current) {
|
|
929
929
|
warnedReachEndPaginationRef.current = true;
|
|
930
930
|
console.warn(
|
|
931
|
-
"TMDataGrid: onReachEnd and pagination slice the same scroll
|
|
931
|
+
"TMDataGrid: onReachEnd and pagination slice the same scroll - the pager caps the rows, so the end it reaches is the page's. Use one or the other.",
|
|
932
932
|
);
|
|
933
933
|
}
|
|
934
934
|
if (lastMountedIndex < rows.length - 1 - reachEndThreshold) return;
|
|
@@ -968,7 +968,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
968
968
|
}));
|
|
969
969
|
// Unresized columns stay fluid (`minmax(min, flex fr)`) so the grid fills the
|
|
970
970
|
// viewport; dragging a resize handle pins that column to an exact pixel width.
|
|
971
|
-
// minWidth is the sum of those floors
|
|
971
|
+
// minWidth is the sum of those floors - anything wider and the grid would
|
|
972
972
|
// force a horizontal scrollbar it doesn't need.
|
|
973
973
|
//
|
|
974
974
|
// Pinned columns are always exact: their sticky offsets come from
|
|
@@ -1050,7 +1050,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1050
1050
|
* The summary row exists by declaration, not by flag: it renders exactly
|
|
1051
1051
|
* when at least one visible leaf column defines a `footer`. Its cells come
|
|
1052
1052
|
* from the leaf header group, whose headers sit in the same left → centre →
|
|
1053
|
-
* right order as `orderedColumns`
|
|
1053
|
+
* right order as `orderedColumns` - so `layoutFor` and the grid tracks
|
|
1054
1054
|
* apply unchanged.
|
|
1055
1055
|
*/
|
|
1056
1056
|
const leafHeaders = headerGroups.at(-1)?.headers ?? [];
|
|
@@ -1059,7 +1059,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1059
1059
|
);
|
|
1060
1060
|
|
|
1061
1061
|
// "row" mode: the row itself is the selection control, with the modifier
|
|
1062
|
-
// conventions of any desktop list
|
|
1062
|
+
// conventions of any desktop list - see resolveRowSelectionClick.
|
|
1063
1063
|
const selectsOnRowClick = features.rowClickSelects;
|
|
1064
1064
|
const rowIsInteractive =
|
|
1065
1065
|
selectsOnRowClick || features.highlightRow || onRowClick !== undefined;
|
|
@@ -1070,7 +1070,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1070
1070
|
* Not squeamishness: TanStack builds a group row on top of its first leaf's
|
|
1071
1071
|
* record (`constructRow(table, id, leafRows[0].original, …)`), so an
|
|
1072
1072
|
* `onRowClick` firing here would hand a consumer a real-looking row that is
|
|
1073
|
-
* the wrong one
|
|
1073
|
+
* the wrong one - a detail panel would open on an arbitrary child. The same
|
|
1074
1074
|
* goes for the highlight, which is what a detail panel follows.
|
|
1075
1075
|
*
|
|
1076
1076
|
* Selecting a group is still possible, through its checkbox, which resolves
|
|
@@ -1138,8 +1138,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1138
1138
|
// Three pieces: the tab stop (which cell the browser can reach), the key
|
|
1139
1139
|
// handler (which cell the state points at), and the effect (which cell the
|
|
1140
1140
|
// DOM actually focuses). They are separate because under virtualization the
|
|
1141
|
-
// three can disagree for a frame
|
|
1142
|
-
// no element yet
|
|
1141
|
+
// three can disagree for a frame - a move to row 4000 names a cell that has
|
|
1142
|
+
// no element yet - and the state is the one that is always right.
|
|
1143
1143
|
// ---------------------------------------------------------------------------
|
|
1144
1144
|
const cellSelection = features.cellSelection;
|
|
1145
1145
|
const cellRangeSelection = features.cellRangeSelection;
|
|
@@ -1150,7 +1150,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1150
1150
|
*
|
|
1151
1151
|
* A ref rather than "is the focus in the grid?" asked at the time, because by
|
|
1152
1152
|
* the time the effect runs the cell the key was pressed on may have unmounted
|
|
1153
|
-
*
|
|
1153
|
+
* (a PageDown scrolls it away) and the focus with it. The intent survives
|
|
1154
1154
|
* where the evidence does not.
|
|
1155
1155
|
*/
|
|
1156
1156
|
const wantsCellFocusRef = useRef(false);
|
|
@@ -1158,8 +1158,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1158
1158
|
const appliedCellRef = useRef<TMDataGridCellPosition | null>(null);
|
|
1159
1159
|
|
|
1160
1160
|
// O(n) per keystroke and per render while a cell is focused. Fine at any row
|
|
1161
|
-
// count a grid actually holds, and the alternative
|
|
1162
|
-
// whenever sorting, filtering or grouping changes
|
|
1161
|
+
// count a grid actually holds, and the alternative - an id→index map rebuilt
|
|
1162
|
+
// whenever sorting, filtering or grouping changes - costs more than it saves.
|
|
1163
1163
|
const focusedRowIndex =
|
|
1164
1164
|
cellSelection && focusedCell !== null
|
|
1165
1165
|
? rows.findIndex((row) => row.id === focusedCell.rowId)
|
|
@@ -1175,8 +1175,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1175
1175
|
/**
|
|
1176
1176
|
* The one cell in the body that Tab can reach.
|
|
1177
1177
|
*
|
|
1178
|
-
* The focused cell when it is on screen. When it is not
|
|
1179
|
-
* been entered, or the user scrolled the focus away with the wheel
|
|
1178
|
+
* The focused cell when it is on screen. When it is not - the grid has never
|
|
1179
|
+
* been entered, or the user scrolled the focus away with the wheel - the
|
|
1180
1180
|
* first mounted cell stands in, so the body always has exactly one tab stop.
|
|
1181
1181
|
* Without the fallback, scrolling the focused row out of the DOM would take
|
|
1182
1182
|
* the grid out of the tab order entirely.
|
|
@@ -1197,7 +1197,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1197
1197
|
) => position !== null && position.rowId === rowId && position.columnId === columnId;
|
|
1198
1198
|
|
|
1199
1199
|
// The rectangle, resolved from ids to the indices the cells are rendered at.
|
|
1200
|
-
// `null` whenever a corner has been filtered or hidden away
|
|
1200
|
+
// `null` whenever a corner has been filtered or hidden away - the range keeps
|
|
1201
1201
|
// its ids, so lifting the filter brings the selection back.
|
|
1202
1202
|
const rangeBounds = resolveRangeBounds({
|
|
1203
1203
|
range: cellRangeSelection ? cellRange : null,
|
|
@@ -1226,7 +1226,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1226
1226
|
* What a copy or an export would take.
|
|
1227
1227
|
*
|
|
1228
1228
|
* The rectangle when there is one, and the focused cell on its own when there
|
|
1229
|
-
* is not
|
|
1229
|
+
* is not - under `"single"` there never is one, and under `"range"` there is
|
|
1230
1230
|
* none until the first gesture. Copying the one cell the user is on is what
|
|
1231
1231
|
* every spreadsheet does, so the two cases collapse into the same block here
|
|
1232
1232
|
* rather than into two paths through the export code.
|
|
@@ -1245,9 +1245,9 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1245
1245
|
/**
|
|
1246
1246
|
* How many of the selected columns have anything to export.
|
|
1247
1247
|
*
|
|
1248
|
-
* The generated lanes are part of the selection
|
|
1248
|
+
* The generated lanes are part of the selection - they take the tint and the
|
|
1249
1249
|
* outline, so the block stays a rectangle and the keyboard still reaches the
|
|
1250
|
-
* checkbox
|
|
1250
|
+
* checkbox - but they hold controls rather than values. A rectangle covering
|
|
1251
1251
|
* only those has nothing to copy, and the items that would say otherwise are
|
|
1252
1252
|
* disabled rather than quietly doing nothing.
|
|
1253
1253
|
*/
|
|
@@ -1276,7 +1276,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1276
1276
|
});
|
|
1277
1277
|
|
|
1278
1278
|
/**
|
|
1279
|
-
* Ctrl+C. Values only, no header row
|
|
1279
|
+
* Ctrl+C. Values only, no header row - Excel's own copy does not include one
|
|
1280
1280
|
* either, and a header pasted into the middle of a sheet is a row of text
|
|
1281
1281
|
* where numbers were expected. The export menu is where headers are offered,
|
|
1282
1282
|
* because a file is a document and a clipboard is a fragment.
|
|
@@ -1291,13 +1291,13 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1291
1291
|
* Whether a drag is laying down a rectangle right now.
|
|
1292
1292
|
*
|
|
1293
1293
|
* State rather than a ref, because the cells only listen for `mouseenter`
|
|
1294
|
-
* while it is true
|
|
1294
|
+
* while it is true - one re-render at each end of the gesture buys a body
|
|
1295
1295
|
* that is not running a handler per cell the rest of the time.
|
|
1296
1296
|
*/
|
|
1297
1297
|
const [isDraggingRange, setIsDraggingRange] = useState(false);
|
|
1298
1298
|
|
|
1299
1299
|
// The drag ends wherever the button comes up, which is routinely outside the
|
|
1300
|
-
// grid
|
|
1300
|
+
// grid - over the scrollbar, or past the window edge. Only the window hears
|
|
1301
1301
|
// that, so only the window can end it.
|
|
1302
1302
|
useEffect(() => {
|
|
1303
1303
|
if (!isDraggingRange) return;
|
|
@@ -1312,7 +1312,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1312
1312
|
) => {
|
|
1313
1313
|
ui.actions.setFocusedCell(position);
|
|
1314
1314
|
setRangeTo(position, { extend });
|
|
1315
|
-
// A Shift+click is a single act, not the start of one
|
|
1315
|
+
// A Shift+click is a single act, not the start of one - there is nothing
|
|
1316
1316
|
// left to drag once the far corner has been named.
|
|
1317
1317
|
if (!extend) setIsDraggingRange(true);
|
|
1318
1318
|
};
|
|
@@ -1331,7 +1331,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1331
1331
|
*
|
|
1332
1332
|
* `extend` is the Shift key: held, the rectangle grows from wherever it was
|
|
1333
1333
|
* anchored; released, it collapses onto the cell just moved to. Collapsing
|
|
1334
|
-
* rather than clearing keeps the invariant the copy path relies on
|
|
1334
|
+
* rather than clearing keeps the invariant the copy path relies on - the
|
|
1335
1335
|
* selection is always the block Ctrl+C would take, even when that block is
|
|
1336
1336
|
* one cell.
|
|
1337
1337
|
*/
|
|
@@ -1351,7 +1351,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1351
1351
|
};
|
|
1352
1352
|
|
|
1353
1353
|
// ---------------------------------------------------------------------------
|
|
1354
|
-
// Cell editing
|
|
1354
|
+
// Cell editing - the gestures. The engine owns the forms; this component
|
|
1355
1355
|
// owns where the editor opens from (keys, double-click) and where the
|
|
1356
1356
|
// focus lands when it closes.
|
|
1357
1357
|
// ---------------------------------------------------------------------------
|
|
@@ -1364,7 +1364,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1364
1364
|
return seed;
|
|
1365
1365
|
};
|
|
1366
1366
|
|
|
1367
|
-
/** DOM focus back onto a cell
|
|
1367
|
+
/** DOM focus back onto a cell - after Escape, or a commit with nowhere to go. */
|
|
1368
1368
|
const refocusCell = (position: TMDataGridCellPosition) => {
|
|
1369
1369
|
const container = scrollContainerRef.current;
|
|
1370
1370
|
if (container === null) return;
|
|
@@ -1387,7 +1387,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1387
1387
|
close: TMDataGridCellEditorClose,
|
|
1388
1388
|
) => {
|
|
1389
1389
|
// Where the keyboard goes: Enter moves down, Tab to the next editable
|
|
1390
|
-
// cell (the deferring batch variants move the same way, draft in tow)
|
|
1390
|
+
// cell (the deferring batch variants move the same way, draft in tow) -
|
|
1391
1391
|
// everything else, and a saved row, goes back to where the edit was.
|
|
1392
1392
|
const move =
|
|
1393
1393
|
close.via === "enter"
|
|
@@ -1439,7 +1439,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1439
1439
|
/**
|
|
1440
1440
|
* Space, from a cell. Checkbox semantics rather than click semantics: it adds
|
|
1441
1441
|
* to the selection instead of replacing it, so a keyboard user builds one the
|
|
1442
|
-
* way they would by ticking boxes
|
|
1442
|
+
* way they would by ticking boxes - which is what the key has taken over from.
|
|
1443
1443
|
*
|
|
1444
1444
|
* Group rows work because the resolver expands a tick to the descendants they
|
|
1445
1445
|
* stand for. A grid with no selection at all falls back to the highlight,
|
|
@@ -1470,7 +1470,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1470
1470
|
/**
|
|
1471
1471
|
* One handler on the grid rather than one per cell: the body mounts a few
|
|
1472
1472
|
* hundred cells and remounts them on every scroll frame, and this closes over
|
|
1473
|
-
* `rows` and `orderedColumns`
|
|
1473
|
+
* `rows` and `orderedColumns` - which every cell would otherwise have to be
|
|
1474
1474
|
* handed just in case it is the one that gets a key.
|
|
1475
1475
|
*
|
|
1476
1476
|
* The cell it is about comes off `event.target`, which is where the
|
|
@@ -1479,7 +1479,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1479
1479
|
const handleGridKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
1480
1480
|
const target = event.target as HTMLElement;
|
|
1481
1481
|
|
|
1482
|
-
// Focus is on something inside a cell
|
|
1482
|
+
// Focus is on something inside a cell - a checkbox, a link, an editor. Its
|
|
1483
1483
|
// keys are its own; the one the grid still answers is the way back out.
|
|
1484
1484
|
if (target.dataset.cell !== "true") {
|
|
1485
1485
|
if (event.key !== "Escape") return;
|
|
@@ -1511,7 +1511,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1511
1511
|
return;
|
|
1512
1512
|
}
|
|
1513
1513
|
|
|
1514
|
-
// Editing keys, ahead of the step-into pair
|
|
1514
|
+
// Editing keys, ahead of the step-into pair - an editable cell's Enter
|
|
1515
1515
|
// opens its editor, and only a non-editable cell's falls through to the
|
|
1516
1516
|
// control it holds.
|
|
1517
1517
|
const keyRow = rows[rowIndex];
|
|
@@ -1527,11 +1527,11 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1527
1527
|
openEditor({ rowId, columnId });
|
|
1528
1528
|
return;
|
|
1529
1529
|
}
|
|
1530
|
-
// The quick single-cell gestures
|
|
1530
|
+
// The quick single-cell gestures - Delete-to-clear and type-to-edit -
|
|
1531
1531
|
// sit out row mode, whose whole point is that nothing commits until
|
|
1532
1532
|
// the row's explicit save.
|
|
1533
1533
|
if (features.editMode !== "row") {
|
|
1534
|
-
// Clear-and-commit, without opening anything
|
|
1534
|
+
// Clear-and-commit, without opening anything - the spreadsheet Delete.
|
|
1535
1535
|
if (event.key === "Delete" || event.key === "Backspace") {
|
|
1536
1536
|
event.preventDefault();
|
|
1537
1537
|
void edit
|
|
@@ -1569,7 +1569,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1569
1569
|
}
|
|
1570
1570
|
|
|
1571
1571
|
// Escape drops the rectangle back to the one cell, as it drops the marching
|
|
1572
|
-
// ants in a spreadsheet. The focus stays where it is
|
|
1572
|
+
// ants in a spreadsheet. The focus stays where it is - Escape is about the
|
|
1573
1573
|
// selection, not about leaving the grid.
|
|
1574
1574
|
if (event.key === "Escape") {
|
|
1575
1575
|
if (cellRange === null) return;
|
|
@@ -1583,7 +1583,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1583
1583
|
// Not routed through `handleRowActivate`, which is the *click* path and only
|
|
1584
1584
|
// selects in `"row"` mode: with the cell cursor on, the checkbox is no
|
|
1585
1585
|
// longer a tab stop (see useCellControlTabIndex), so this is the keyboard's
|
|
1586
|
-
// way to a selection under every mode that has one
|
|
1586
|
+
// way to a selection under every mode that has one - checkbox included.
|
|
1587
1587
|
if (event.key === " ") {
|
|
1588
1588
|
event.preventDefault();
|
|
1589
1589
|
const row = rows[rowIndex];
|
|
@@ -1609,7 +1609,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1609
1609
|
// did not move.
|
|
1610
1610
|
event.preventDefault();
|
|
1611
1611
|
if (next.rowIndex === rowIndex && next.columnIndex === columnIndex) return;
|
|
1612
|
-
// Shift extends the rectangle instead of collapsing it
|
|
1612
|
+
// Shift extends the rectangle instead of collapsing it - the same key that
|
|
1613
1613
|
// extends a text selection, a file list and a spreadsheet range.
|
|
1614
1614
|
moveFocusedCell(next, { extend: event.shiftKey && cellRangeSelection });
|
|
1615
1615
|
};
|
|
@@ -1630,7 +1630,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1630
1630
|
|
|
1631
1631
|
if (focusedCell !== appliedCellRef.current) {
|
|
1632
1632
|
appliedCellRef.current = focusedCell;
|
|
1633
|
-
// Bring the row on screen first
|
|
1633
|
+
// Bring the row on screen first - this is what makes a cell that is not
|
|
1634
1634
|
// mounted focusable at all, one frame later.
|
|
1635
1635
|
if (focusedRowIndex >= 0) {
|
|
1636
1636
|
virtualizer.scrollToIndex(focusedRowIndex, { align: "auto" });
|
|
@@ -1643,7 +1643,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1643
1643
|
|
|
1644
1644
|
// Never take the focus from elsewhere on the page. A consumer moving the
|
|
1645
1645
|
// cell while the user is typing in a form somewhere means "move the ring",
|
|
1646
|
-
// not "move the caret"
|
|
1646
|
+
// not "move the caret" - the scroll above already did the visible part.
|
|
1647
1647
|
if (
|
|
1648
1648
|
!wantsCellFocusRef.current &&
|
|
1649
1649
|
!container.contains(document.activeElement)
|
|
@@ -1655,7 +1655,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1655
1655
|
// Still scrolling. The intent stays up, and the next render tries again.
|
|
1656
1656
|
if (cellElement === null) return;
|
|
1657
1657
|
wantsCellFocusRef.current = false;
|
|
1658
|
-
// Focus is already inside
|
|
1658
|
+
// Focus is already inside - on the control Enter stepped into. The cell is
|
|
1659
1659
|
// where the ring belongs, so there is nothing to move.
|
|
1660
1660
|
if (cellElement.contains(document.activeElement)) return;
|
|
1661
1661
|
|
|
@@ -1693,8 +1693,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1693
1693
|
// A right-click inside the selection is about the selection and leaves it
|
|
1694
1694
|
// alone; one outside moves it first, so the menu never offers to copy a
|
|
1695
1695
|
// block somewhere else on the screen. Spreadsheets and file managers both
|
|
1696
|
-
// behave this way, and the alternative
|
|
1697
|
-
// see the outline of
|
|
1696
|
+
// behave this way, and the alternative - exporting cells the user cannot
|
|
1697
|
+
// see the outline of - is the kind of surprise that ends in a support call.
|
|
1698
1698
|
if (cellSelection && rowId !== null && columnId !== null) {
|
|
1699
1699
|
const rowIndex = rows.findIndex((row) => row.id === rowId);
|
|
1700
1700
|
const columnIndex = orderedColumns.findIndex(
|
|
@@ -1719,7 +1719,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1719
1719
|
* opened menu rather than once per mounted row. `null` back from it means
|
|
1720
1720
|
* this row has no menu, which is also how the menu stays shut.
|
|
1721
1721
|
*
|
|
1722
|
-
* The casts put back the concrete row type the context boundary erased
|
|
1722
|
+
* The casts put back the concrete row type the context boundary erased - the
|
|
1723
1723
|
* same crossing `onRowClick` makes.
|
|
1724
1724
|
*/
|
|
1725
1725
|
const rowMenuContent =
|
|
@@ -1743,7 +1743,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1743
1743
|
*
|
|
1744
1744
|
* Offered under `"range"` only. Under `"single"` the selection is always the
|
|
1745
1745
|
* one cell the user is standing on, and a menu whose every item acts on it
|
|
1746
|
-
* would be a menu about nothing much
|
|
1746
|
+
* would be a menu about nothing much - Ctrl+C still copies it.
|
|
1747
1747
|
*/
|
|
1748
1748
|
const cellMenuContent =
|
|
1749
1749
|
cellRangeSelection && contextMenuTarget !== null && selectionBounds !== null ? (
|
|
@@ -1795,7 +1795,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1795
1795
|
|
|
1796
1796
|
/**
|
|
1797
1797
|
* Which edges the scroll position is currently at. Seeded "at top-left",
|
|
1798
|
-
* which is where a grid mounts
|
|
1798
|
+
* which is where a grid mounts - so the callbacks report *arrivals*, and
|
|
1799
1799
|
* mounting fires nothing.
|
|
1800
1800
|
*/
|
|
1801
1801
|
const scrollEdgesRef = useRef({
|
|
@@ -1812,7 +1812,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1812
1812
|
|
|
1813
1813
|
/**
|
|
1814
1814
|
* The context menu's dropdown is anchored to a fixed viewport point, so it
|
|
1815
|
-
* does not travel with the row when the body scrolls
|
|
1815
|
+
* does not travel with the row when the body scrolls - and under
|
|
1816
1816
|
* virtualization the row it belongs to may unmount entirely. Closing is
|
|
1817
1817
|
* the honest answer, and matches what every desktop context menu does.
|
|
1818
1818
|
*
|
|
@@ -1860,7 +1860,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1860
1860
|
aria-label={ariaLabel}
|
|
1861
1861
|
aria-labelledby={ariaLabelledBy}
|
|
1862
1862
|
// Set while `meta.loading` is, whether or not the body has rows to
|
|
1863
|
-
// show
|
|
1863
|
+
// show - a refetch over existing rows is still a fetch, and a test
|
|
1864
1864
|
// waiting for the grid to settle has nothing else to wait on.
|
|
1865
1865
|
aria-busy={loading === true ? true : undefined}
|
|
1866
1866
|
aria-multiselectable={
|
|
@@ -1884,7 +1884,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1884
1884
|
(hasSummaryRow ? 1 : 0)
|
|
1885
1885
|
}
|
|
1886
1886
|
aria-colcount={orderedColumns.length}
|
|
1887
|
-
// Body rows the grid is currently showing
|
|
1887
|
+
// Body rows the grid is currently showing - the page under
|
|
1888
1888
|
// pagination, everything the filters left otherwise, edge blocks
|
|
1889
1889
|
// included. Most of them are not in the DOM, so this is what a test
|
|
1890
1890
|
// waits on rather than counting nodes.
|
|
@@ -1899,7 +1899,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1899
1899
|
aria-rowindex={groupIndex + 1}
|
|
1900
1900
|
data-dg-header-row
|
|
1901
1901
|
// The scrolled-under shadow belongs to the boundary between
|
|
1902
|
-
// header and body
|
|
1902
|
+
// header and body - the last header row, not every stacked
|
|
1903
1903
|
// group row above it. See the stylesheet.
|
|
1904
1904
|
data-dg-header-last={
|
|
1905
1905
|
groupIndex === headerGroups.length - 1 || undefined
|
|
@@ -1917,7 +1917,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1917
1917
|
))}
|
|
1918
1918
|
</div>
|
|
1919
1919
|
|
|
1920
|
-
{/* The entry block
|
|
1920
|
+
{/* The entry block - new rows being typed, stuck under the header.
|
|
1921
1921
|
Present only while `edit.addRow()` has open entries. */}
|
|
1922
1922
|
{features.editing && newRowCount > 0 && (
|
|
1923
1923
|
<TMDataGridEntryRows
|
|
@@ -1940,7 +1940,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1940
1940
|
/**
|
|
1941
1941
|
* One renderer for every body row, wherever it sits: the
|
|
1942
1942
|
* virtualized centre, or a pinned edge block. `viewIndex` is the
|
|
1943
|
-
* row's index in the scrolling view
|
|
1943
|
+
* row's index in the scrolling view - `-1` for a pinned row,
|
|
1944
1944
|
* which takes it out of striping, row measurement and the cell
|
|
1945
1945
|
* range, all of which are statements about the scrolling order.
|
|
1946
1946
|
* Scoped here rather than a component so the row keeps closing
|
|
@@ -1971,7 +1971,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1971
1971
|
)?.column.id ?? null)
|
|
1972
1972
|
: null;
|
|
1973
1973
|
// Cell selection takes the body's tab stop off the row and puts
|
|
1974
|
-
// it on a cell
|
|
1974
|
+
// it on a cell - two stops per row would make Tab a way of
|
|
1975
1975
|
// walking the grid, which is what the arrow keys are for. Space
|
|
1976
1976
|
// on a cell keeps the selection reachable; see handleGridKeyDown.
|
|
1977
1977
|
const takesKeyboard =
|
|
@@ -1987,13 +1987,13 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1987
1987
|
data-row-id={row.id}
|
|
1988
1988
|
// Only measured when details are on: without them every row
|
|
1989
1989
|
// is exactly `rowHeight`, and the estimate above is already
|
|
1990
|
-
// the answer
|
|
1990
|
+
// the answer - so a plain grid keeps its ResizeObserver-free
|
|
1991
1991
|
// body. `measureElement` is a stable instance method and
|
|
1992
1992
|
// returns nothing, so it can be the ref itself.
|
|
1993
1993
|
//
|
|
1994
1994
|
// `data-index` is how the virtualizer maps the observed
|
|
1995
1995
|
// element back to its item. The panel is inside this element,
|
|
1996
|
-
// so what gets measured is the row *and* its detail
|
|
1996
|
+
// so what gets measured is the row *and* its detail - which
|
|
1997
1997
|
// is the whole reason no second virtual item is needed.
|
|
1998
1998
|
data-index={
|
|
1999
1999
|
renderDetails && viewIndex >= 0 ? viewIndex : undefined
|
|
@@ -2004,7 +2004,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2004
2004
|
: undefined
|
|
2005
2005
|
}
|
|
2006
2006
|
// The tree's own rows. `data-grouped` rather than a class, to
|
|
2007
|
-
// match how the rest of the row's state is published
|
|
2007
|
+
// match how the rest of the row's state is published - and so
|
|
2008
2008
|
// a consumer can restyle them without reaching into modules.
|
|
2009
2009
|
data-grouped={isGroupRow}
|
|
2010
2010
|
data-depth={row.depth}
|
|
@@ -2012,8 +2012,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2012
2012
|
data-pinned={pinnedAt}
|
|
2013
2013
|
// Gated on the flag, not just on `getIsSelected()`: under
|
|
2014
2014
|
// `"highlight"` there is no selection, but `rowSelection` may
|
|
2015
|
-
// still hold ids from before the mode changed
|
|
2016
|
-
// prunes it
|
|
2015
|
+
// still hold ids from before the mode changed - TanStack never
|
|
2016
|
+
// prunes it - and a row must not report itself selected in a
|
|
2017
2017
|
// mode where selecting is not a thing.
|
|
2018
2018
|
data-selected={features.rowSelection && row.getIsSelected()}
|
|
2019
2019
|
// Being selected is state; painting it is a display choice, so
|
|
@@ -2024,7 +2024,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2024
2024
|
row.getIsSelected()
|
|
2025
2025
|
}
|
|
2026
2026
|
// The highlighted row is its own concept, so its own attribute
|
|
2027
|
-
// pair
|
|
2027
|
+
// pair - `data-highlighted` / `aria-current` against
|
|
2028
2028
|
// `data-selected` / `aria-selected` for selection. Under
|
|
2029
2029
|
// `"checkboxAndHighlight"` a row can carry both.
|
|
2030
2030
|
//
|
|
@@ -2057,7 +2057,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2057
2057
|
: undefined
|
|
2058
2058
|
}
|
|
2059
2059
|
// From the row's position in the whole view, so the stripes
|
|
2060
|
-
// survive the virtualizer's moving window
|
|
2060
|
+
// survive the virtualizer's moving window - see the prop.
|
|
2061
2061
|
data-striped={
|
|
2062
2062
|
striped && viewIndex >= 0 ? viewIndex % 2 === 1 : undefined
|
|
2063
2063
|
}
|
|
@@ -2106,7 +2106,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2106
2106
|
}
|
|
2107
2107
|
: undefined
|
|
2108
2108
|
}
|
|
2109
|
-
// Bubbles to the rowgroup, where the menu opens
|
|
2109
|
+
// Bubbles to the rowgroup, where the menu opens - so the row
|
|
2110
2110
|
// is recorded before the dropdown is built.
|
|
2111
2111
|
//
|
|
2112
2112
|
// A group row records itself only for the cell menu, which is
|
|
@@ -2114,7 +2114,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2114
2114
|
// called for one: TanStack builds a group row on its first
|
|
2115
2115
|
// child's record, so a render prop reaching for `row.original`
|
|
2116
2116
|
// would be handed a real employee that has nothing to do with
|
|
2117
|
-
// the group
|
|
2117
|
+
// the group - see where the content is built. The browser menu
|
|
2118
2118
|
// stays suppressed over them either way.
|
|
2119
2119
|
onContextMenu={
|
|
2120
2120
|
(rowContextMenu && !isGroupRow) || cellRangeSelection
|
|
@@ -2204,7 +2204,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2204
2204
|
? 0
|
|
2205
2205
|
: -1,
|
|
2206
2206
|
// "Selected" means "in the block Ctrl+C would
|
|
2207
|
-
// take"
|
|
2207
|
+
// take" - which under `"single"` is the one cell
|
|
2208
2208
|
// the user is standing on.
|
|
2209
2209
|
selected: cellRangeSelection
|
|
2210
2210
|
? isWithinBounds(rangeBounds, viewIndex, cellIndex)
|
|
@@ -2267,7 +2267,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2267
2267
|
})}
|
|
2268
2268
|
|
|
2269
2269
|
{/* A second grid row inside the row element, on the same
|
|
2270
|
-
subgrid tracks
|
|
2270
|
+
subgrid tracks - which is what lets one measurement cover
|
|
2271
2271
|
both. A cell spanning every column rather than a row of
|
|
2272
2272
|
its own, so the row keeps holding exactly one row's worth
|
|
2273
2273
|
of content and `aria-rowcount` still counts records. */}
|
|
@@ -2280,7 +2280,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2280
2280
|
className={classes.detailsCell}
|
|
2281
2281
|
// The row underneath may select, highlight or open a
|
|
2282
2282
|
// context menu. A panel is content, not part of that
|
|
2283
|
-
// gesture surface
|
|
2283
|
+
// gesture surface - and stopping the right-click here is
|
|
2284
2284
|
// what leaves the browser's own menu working for a link
|
|
2285
2285
|
// or an input inside it.
|
|
2286
2286
|
onClick={(event) => event.stopPropagation()}
|
|
@@ -2300,7 +2300,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2300
2300
|
return (
|
|
2301
2301
|
<>
|
|
2302
2302
|
{/* The pinned edge blocks. Same sticky mechanics as the
|
|
2303
|
-
entry block
|
|
2303
|
+
entry block - the *block* is the sticky element, its rows
|
|
2304
2304
|
flow normally inside it. `role="presentation"` keeps the
|
|
2305
2305
|
wrapper out of the accessibility tree, so the rows stay
|
|
2306
2306
|
direct children of the rowgroup. Inside the rowgroup so
|
|
@@ -2378,7 +2378,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2378
2378
|
its way, "empty" would be a lie), open entry rows show only
|
|
2379
2379
|
themselves (a "no rows" under the row being typed reads as a
|
|
2380
2380
|
contradiction), then the consumer's blank slate, then the two
|
|
2381
|
-
defaults
|
|
2381
|
+
defaults - filtered-empty with its search icon, truly-empty as
|
|
2382
2382
|
plain text, because only one of them is the user's own doing. */}
|
|
2383
2383
|
{isEmpty && loading === true && (
|
|
2384
2384
|
<div role="row" className={classes.messageRow}>
|
|
@@ -2396,12 +2396,12 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2396
2396
|
) : hasActiveFilters ? (
|
|
2397
2397
|
<>
|
|
2398
2398
|
<SearchIcon size={40} stroke={1.4} opacity={0.4} />
|
|
2399
|
-
<Text size={controlSize} c="dimmed">
|
|
2399
|
+
<Text span size={controlSize} c="dimmed">
|
|
2400
2400
|
{noResultsLabel}
|
|
2401
2401
|
</Text>
|
|
2402
2402
|
</>
|
|
2403
2403
|
) : (
|
|
2404
|
-
<Text size={controlSize} c="dimmed">
|
|
2404
|
+
<Text span size={controlSize} c="dimmed">
|
|
2405
2405
|
{labels.noRows}
|
|
2406
2406
|
</Text>
|
|
2407
2407
|
)}
|