@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
|
@@ -35,7 +35,7 @@ export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
|
|
|
35
35
|
className?: string;
|
|
36
36
|
/**
|
|
37
37
|
* Inline styles. Widened to accept CSS variables, which is how the grid's
|
|
38
|
-
* colours are themed
|
|
38
|
+
* colours are themed - `--dg-row-selected-bg` for the selected row
|
|
39
39
|
* background. Plain `CSSProperties` rejects `--*` keys.
|
|
40
40
|
*/
|
|
41
41
|
style?: CSSProperties & Record<`--${string}`, string | number>;
|
|
@@ -44,7 +44,7 @@ export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
|
|
|
44
44
|
/**
|
|
45
45
|
* Names this grid for tests. The grid names its own pieces with
|
|
46
46
|
* `data-dg-part` rather than minting test ids, and those repeat across
|
|
47
|
-
* grids
|
|
47
|
+
* grids - scope through this one and they stop colliding:
|
|
48
48
|
*
|
|
49
49
|
* ```ts
|
|
50
50
|
* page.getByTestId("orders").locator('[data-dg-part="row"][data-row-id="42"]')
|
|
@@ -56,7 +56,7 @@ export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Root of the grid. Takes the object returned by `useTMDataGrid`
|
|
59
|
+
* Root of the grid. Takes the object returned by `useTMDataGrid` - spread it -
|
|
60
60
|
* and publishes it to the compound components below it:
|
|
61
61
|
*
|
|
62
62
|
* ```tsx
|
|
@@ -133,7 +133,7 @@ function TMDataGridRoot<TData extends RowData>({
|
|
|
133
133
|
<div
|
|
134
134
|
id={id}
|
|
135
135
|
data-testid={testId}
|
|
136
|
-
// The handle everything else scopes off
|
|
136
|
+
// The handle everything else scopes off - see the `data-testid` prop.
|
|
137
137
|
data-dg-root
|
|
138
138
|
data-size={size}
|
|
139
139
|
className={[classes.root, className].filter(Boolean).join(" ")}
|
|
@@ -160,7 +160,7 @@ export const TMDataGrid = Object.assign(TMDataGridRoot, {
|
|
|
160
160
|
FilterPanel: TMDataGridFilterPanel,
|
|
161
161
|
/**
|
|
162
162
|
* Takes the grid as an `api` prop rather than from context, so it can be
|
|
163
|
-
* rendered outside `<TMDataGrid>`
|
|
163
|
+
* rendered outside `<TMDataGrid>` - a page header, for instance.
|
|
164
164
|
*/
|
|
165
165
|
FilterPills: TMDataGridFilterPills,
|
|
166
166
|
/** Rendered by `TMDataGrid.ColumnsButton`; exported for custom layouts. */
|
|
@@ -42,11 +42,11 @@ const BUILT_IN_EDITORS: Record<
|
|
|
42
42
|
export type TMDataGridCellEditorClose =
|
|
43
43
|
| { committed: true; via: "enter" | "tab" | "shift-tab" | "pick" }
|
|
44
44
|
| { committed: false; via: "escape" }
|
|
45
|
-
/** Batch: the editor closed, the draft stays
|
|
45
|
+
/** Batch: the editor closed, the draft stays - Enter parks, Tab moves on. */
|
|
46
46
|
| { committed: false; via: "defer" | "defer-tab" | "defer-shift-tab" };
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* The host an editing cell mounts
|
|
49
|
+
* The host an editing cell mounts - it owns the field, the keys and the blur
|
|
50
50
|
* policy; the editor inside it (a built-in picked by `meta.type`, or the
|
|
51
51
|
* column's `meta.editor`) owns the input.
|
|
52
52
|
*
|
|
@@ -66,7 +66,7 @@ export function TMDataGridCellEditor({
|
|
|
66
66
|
takeSeedText: () => string | undefined;
|
|
67
67
|
/** In row mode only the first editable cell takes the focus. */
|
|
68
68
|
autoFocus?: boolean;
|
|
69
|
-
/** After the editor closed itself
|
|
69
|
+
/** After the editor closed itself - the table moves the focus. */
|
|
70
70
|
onClose: (args: TMDataGridCellEditorClose) => void;
|
|
71
71
|
}) {
|
|
72
72
|
const { table, edit, features, labels, controlSize } = useTMDataGridContext();
|
|
@@ -164,7 +164,7 @@ export function TMDataGridCellEditor({
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
|
-
* Focus left the editor entirely
|
|
167
|
+
* Focus left the editor entirely - a click somewhere else. Under `"cell"`
|
|
168
168
|
* that commits (Sheets); a commit blocked by validation keeps the form and
|
|
169
169
|
* its invalid marker, with the editor closed. The confirming modes keep
|
|
170
170
|
* the draft open-but-inactive, waiting for their explicit ✓.
|
|
@@ -82,7 +82,7 @@ export function TMDataGridColumnsPanel() {
|
|
|
82
82
|
/>
|
|
83
83
|
))}
|
|
84
84
|
{visibleInPanel.length === 0 && (
|
|
85
|
-
<Text size={controlSize} c="dimmed">
|
|
85
|
+
<Text span size={controlSize} c="dimmed">
|
|
86
86
|
{labels.columnsNoMatch(search)}
|
|
87
87
|
</Text>
|
|
88
88
|
)}
|
|
@@ -102,7 +102,7 @@ export function TMDataGridColumnsPanel() {
|
|
|
102
102
|
/>
|
|
103
103
|
{/* The whole layout, not only visibility: one reset with an honest
|
|
104
104
|
scope, stated in the tooltip. `table.resetColumnVisibility()` would
|
|
105
|
-
also be wrong under persistence
|
|
105
|
+
also be wrong under persistence - it resets to `initialState`,
|
|
106
106
|
which the mount built *from* the persisted payload. */}
|
|
107
107
|
<Tooltip label={labels.columnsResetHint} withArrow>
|
|
108
108
|
<Button
|
|
@@ -31,7 +31,7 @@ function DetailsCell<TData extends RowData>({
|
|
|
31
31
|
// cell, not by Tab, or the grid would have one tab stop per mounted row.
|
|
32
32
|
const tabIndex = useCellControlTabIndex();
|
|
33
33
|
|
|
34
|
-
// Group rows open into their children, not into a panel
|
|
34
|
+
// Group rows open into their children, not into a panel - see the body's
|
|
35
35
|
// `showsDetails`. Their cells are aggregated, so this lane is already blank
|
|
36
36
|
// on them; the guard is what makes that a decision rather than a side effect.
|
|
37
37
|
if (row.getIsGrouped()) return null;
|
|
@@ -63,7 +63,7 @@ function DetailsCell<TData extends RowData>({
|
|
|
63
63
|
* selects and clears every row.
|
|
64
64
|
*
|
|
65
65
|
* Deliberately not `table.toggleAllRowsExpanded()`: that writes the `expanded`
|
|
66
|
-
* state's whole-table form, and one state holds both the tree and the panels
|
|
66
|
+
* state's whole-table form, and one state holds both the tree and the panels -
|
|
67
67
|
* so it would unfold every group as well. Only the data rows are touched here;
|
|
68
68
|
* whatever the tree was showing, it goes on showing. See resolveExpandAll.
|
|
69
69
|
*/
|
|
@@ -94,7 +94,7 @@ function DetailsHeader<TData extends RowData>({
|
|
|
94
94
|
}
|
|
95
95
|
data-dg-part="details-toggle-all"
|
|
96
96
|
// Partly expanded opens the rest rather than closing what is already
|
|
97
|
-
// open
|
|
97
|
+
// open - the same reading as an indeterminate select-all box.
|
|
98
98
|
onClick={() =>
|
|
99
99
|
table.setExpanded(
|
|
100
100
|
resolveExpandAll({
|
|
@@ -118,7 +118,7 @@ function DetailsHeader<TData extends RowData>({
|
|
|
118
118
|
*
|
|
119
119
|
* Structural, like the checkbox and tree columns: fixed width, pinned to the
|
|
120
120
|
* left after both of them, not hideable, not movable and not resizable. A
|
|
121
|
-
* toggle that wandered off to the right of the grid
|
|
121
|
+
* toggle that wandered off to the right of the grid (or hid itself) would
|
|
122
122
|
* leave rows with panels no one can open.
|
|
123
123
|
*
|
|
124
124
|
* Last of the three because it acts on one record: the checkbox picks rows out
|
|
@@ -140,7 +140,7 @@ export function createDetailsColumn<TData extends RowData>(
|
|
|
140
140
|
enableOrdering: false,
|
|
141
141
|
},
|
|
142
142
|
// A system lane: as wide as the control it holds and no wider. Fixed at
|
|
143
|
-
// every scale
|
|
143
|
+
// every scale - the control does not grow with the font size, so neither
|
|
144
144
|
// should its track.
|
|
145
145
|
size: 36,
|
|
146
146
|
minSize: 36,
|
|
@@ -157,7 +157,7 @@ export function createDetailsColumn<TData extends RowData>(
|
|
|
157
157
|
header: ({ table }) => <DetailsHeader table={table} />,
|
|
158
158
|
cell: ({ row }) => <DetailsCell row={row} />,
|
|
159
159
|
// Deliberately no `aggregatedCell`: on a group row every cell outside the
|
|
160
|
-
// grouped column counts as aggregated, and blank is the right answer here
|
|
160
|
+
// grouped column counts as aggregated, and blank is the right answer here -
|
|
161
161
|
// groups expand into their rows, not into a panel.
|
|
162
162
|
};
|
|
163
163
|
}
|
|
@@ -5,8 +5,8 @@ import { useTMDataGridContext } from "../TMDataGridContext";
|
|
|
5
5
|
/**
|
|
6
6
|
* Batch mode's toolbar chrome: Save with the dirty-row count, and Discard.
|
|
7
7
|
* Both read the edit store, so they grey out while nothing is dirty and the
|
|
8
|
-
* Save spins while a submit is in flight. Works under any `editMode`
|
|
9
|
-
* cellConfirm grid accumulating drafts can offer the same pair
|
|
8
|
+
* Save spins while a submit is in flight. Works under any `editMode` - a
|
|
9
|
+
* cellConfirm grid accumulating drafts can offer the same pair - and renders
|
|
10
10
|
* nothing while editing is off.
|
|
11
11
|
*
|
|
12
12
|
* ```tsx
|
|
@@ -21,7 +21,7 @@ export const EDIT_COLUMN_ID = "__edit__";
|
|
|
21
21
|
* One row's slot in the edit lane. Three states, driven off the edit store:
|
|
22
22
|
* a pencil while idle, Save/Cancel while the row's form is open, a loader
|
|
23
23
|
* while its commit is in flight. A row error turns the Save red and puts the
|
|
24
|
-
* message in its tooltip
|
|
24
|
+
* message in its tooltip - the pathless `.refine()` has nowhere else to land.
|
|
25
25
|
*/
|
|
26
26
|
function EditLaneCell<TData extends RowData>({
|
|
27
27
|
row,
|
|
@@ -79,7 +79,7 @@ function EditLaneCell<TData extends RowData>({
|
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// A batch deletion mark is a draft like any other
|
|
82
|
+
// A batch deletion mark is a draft like any other - restorable in place.
|
|
83
83
|
if (isMarkedDeleted) {
|
|
84
84
|
return (
|
|
85
85
|
<ActionIcon
|
|
@@ -204,7 +204,7 @@ function EditLaneCell<TData extends RowData>({
|
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
206
|
* The generated edit lane, appended and pinned right under
|
|
207
|
-
* `editMode: "row"`
|
|
207
|
+
* `editMode: "row"` - the row's Save at the end of the row, mirroring the
|
|
208
208
|
* checkbox lane's build on the left.
|
|
209
209
|
*/
|
|
210
210
|
export function createEditColumn<TData extends RowData>(
|
|
@@ -229,7 +229,7 @@ export function createEditColumn<TData extends RowData>(
|
|
|
229
229
|
enablePinning: false,
|
|
230
230
|
header: () => null,
|
|
231
231
|
cell: ({ row }) => <EditLaneCell row={row} />,
|
|
232
|
-
// Group rows: same reasoning as the checkbox lane
|
|
232
|
+
// Group rows: same reasoning as the checkbox lane - without this the
|
|
233
233
|
// cell renders blank on aggregated rows, but here blank is also correct,
|
|
234
234
|
// so the aggregated cell renders the same (null for groups).
|
|
235
235
|
aggregatedCell: ({ row }) => <EditLaneCell row={row} />,
|
|
@@ -23,7 +23,7 @@ type ErasedColumn = Column<TMDataGridFeatures, TMDataGridRowData, unknown>;
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Whether an entry cell edits. `edit.canEditCell` asks the main table for
|
|
26
|
-
* the row, and an entry row is precisely not there yet
|
|
26
|
+
* the row, and an entry row is precisely not there yet - so the structural
|
|
27
27
|
* half of the same rule is applied to the entry table's own row.
|
|
28
28
|
*/
|
|
29
29
|
function isEntryCellEditable(
|
|
@@ -39,13 +39,13 @@ function isEntryCellEditable(
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* The sticky entry block
|
|
42
|
+
* The sticky entry block - one row of open editors per `edit.addRow()`,
|
|
43
43
|
* pinned under the header. The one place stickiness is genuinely required:
|
|
44
44
|
* an existing row that scrolls away has a place to scroll back to, a row
|
|
45
45
|
* being typed into does not exist anywhere else.
|
|
46
46
|
*
|
|
47
|
-
* A second, tiny table instance over the new rows' seed values
|
|
48
|
-
* columns, its own `useTable`, no sorting or filtering ever exercised
|
|
47
|
+
* A second, tiny table instance over the new rows' seed values - same
|
|
48
|
+
* columns, its own `useTable`, no sorting or filtering ever exercised - so
|
|
49
49
|
* these are real `Cell`s and the editor host, the typed editors and the
|
|
50
50
|
* validators all apply unchanged. The forms live in the same engine map as
|
|
51
51
|
* every other draft, keyed by their temporary ids.
|
|
@@ -67,7 +67,7 @@ export function TMDataGridEntryRows({
|
|
|
67
67
|
const newRows = useSelector(edit.store, (state) => state.newRows);
|
|
68
68
|
|
|
69
69
|
// The seed values, frozen at addRow: the live values belong to the forms,
|
|
70
|
-
// which the editors read directly
|
|
70
|
+
// which the editors read directly - this table only provides row and cell
|
|
71
71
|
// identity.
|
|
72
72
|
const data = useMemo(
|
|
73
73
|
() =>
|
|
@@ -150,7 +150,7 @@ export function TMDataGridEntryRows({
|
|
|
150
150
|
onClose={() => {}}
|
|
151
151
|
/>
|
|
152
152
|
) : cell !== undefined && column.id === EDIT_COLUMN_ID ? (
|
|
153
|
-
// The lane's cell
|
|
153
|
+
// The lane's cell - the entry row's ✓/✕ pair.
|
|
154
154
|
flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
155
155
|
) : null}
|
|
156
156
|
</div>
|
|
@@ -38,7 +38,7 @@ function asFilterValue(value: unknown): TMDataGridFilterValue {
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* The three value shapes an operator can take. A typed value survives an
|
|
41
|
-
* operator or column change only within its shape
|
|
41
|
+
* operator or column change only within its shape - a set is not a range,
|
|
42
42
|
* even though both are arrays.
|
|
43
43
|
*/
|
|
44
44
|
function valueShape(
|
|
@@ -53,7 +53,7 @@ function valueShape(
|
|
|
53
53
|
* The MUI-style filter surface: one row per active column filter, each row a
|
|
54
54
|
* column / operator / value triple. It only ever reads and writes the table's
|
|
55
55
|
* `columnFilters` state, so a `manualFiltering` grid gets the same panel for
|
|
56
|
-
* free
|
|
56
|
+
* free - the state is forwarded to the server instead of a row model.
|
|
57
57
|
*/
|
|
58
58
|
export function TMDataGridFilterPanel() {
|
|
59
59
|
const { table, ui, labels, controlSize } = useTMDataGridContext();
|
|
@@ -110,7 +110,7 @@ export function TMDataGridFilterPanel() {
|
|
|
110
110
|
? getColumnDefaultOperator(target)
|
|
111
111
|
: getDefaultOperator("string");
|
|
112
112
|
// The typed value only survives the move while it still fits the new
|
|
113
|
-
// operator
|
|
113
|
+
// operator - a text needle has no meaning to an `isAnyOf` set, or a set
|
|
114
114
|
// to a text input.
|
|
115
115
|
const value =
|
|
116
116
|
valueShape(operator) === valueShape(current.operator)
|
|
@@ -200,7 +200,7 @@ export function TMDataGridFilterPanel() {
|
|
|
200
200
|
}}
|
|
201
201
|
>
|
|
202
202
|
<div className={classes.filterPanelHeader}>
|
|
203
|
-
<Text size={controlSize} fw={600}>
|
|
203
|
+
<Text span size={controlSize} fw={600}>
|
|
204
204
|
{labels.filters}
|
|
205
205
|
</Text>
|
|
206
206
|
<ActionIcon
|
|
@@ -222,7 +222,7 @@ export function TMDataGridFilterPanel() {
|
|
|
222
222
|
const type = column ? getColumnType(column) : "string";
|
|
223
223
|
const needsValue = operatorNeedsValue(value.operator);
|
|
224
224
|
const scalarValue = typeof value.value === "string" ? value.value : "";
|
|
225
|
-
// Pre-resolved only where options mean something out of the box
|
|
225
|
+
// Pre-resolved only where options mean something out of the box -
|
|
226
226
|
// a declared set, or a select-shaped column's faceted values. A
|
|
227
227
|
// custom control wanting faceted values elsewhere resolves them
|
|
228
228
|
// itself; resolving here would build the faceted index for every
|
|
@@ -293,7 +293,7 @@ export function TMDataGridFilterPanel() {
|
|
|
293
293
|
|
|
294
294
|
{column ? (
|
|
295
295
|
// The value slot: `meta.filterControl` if the column declares
|
|
296
|
-
// one, the built-in shape-by-operator input otherwise
|
|
296
|
+
// one, the built-in shape-by-operator input otherwise - both
|
|
297
297
|
// through the same value-only contract.
|
|
298
298
|
<ValueControl
|
|
299
299
|
column={column}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
gap: var(--mantine-spacing-xs);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/* Mantine's dark-scheme pill is `dark-7`
|
|
10
|
+
/* Mantine's dark-scheme pill is `dark-7` - the same colour as the body - so a
|
|
11
11
|
pill sitting on the page rather than inside a `PillsInput` disappears. Both
|
|
12
12
|
schemes are restated here so the two stay a pair, with a border to keep the
|
|
13
13
|
shape readable against any surface. Scoped through `.filterPills` for the
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/* The label is a button so a pill can be clicked back open, but it must read as
|
|
26
|
-
part of the pill
|
|
26
|
+
part of the pill - Mantine's own label styles carry the font. */
|
|
27
27
|
.pillLabel {
|
|
28
28
|
font: inherit;
|
|
29
29
|
color: inherit;
|
|
@@ -27,7 +27,7 @@ export type TMDataGridFilterPillsProps<TData extends RowData> = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* One pill per active filter
|
|
30
|
+
* One pill per active filter (`First name: Sofia ✕`), with the ✕ clearing that
|
|
31
31
|
* filter and a click on the label opening the filter panel on its column.
|
|
32
32
|
*
|
|
33
33
|
* The one grid component that takes the api as a prop instead of reading
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
/* Grouping suspends paging. The pager stays put and fades rather than
|
|
13
13
|
disappearing, so the footer reads as switched off rather than as having
|
|
14
|
-
vanished
|
|
14
|
+
vanished - and the row it holds does not change height under the grid. Its
|
|
15
15
|
controls carry their own `disabled`; this is the label half. */
|
|
16
16
|
.footer[data-paging-suspended="true"] {
|
|
17
17
|
opacity: 0.55;
|
|
@@ -61,11 +61,11 @@ export type TMDataGridFooterProps = {
|
|
|
61
61
|
* MUI-style pager: "Rows per page · from–to of total · ‹ ›".
|
|
62
62
|
*
|
|
63
63
|
* Renders nothing unless pagination is enabled (`enablePagination` or
|
|
64
|
-
* `manualPagination`)
|
|
64
|
+
* `manualPagination`) - the footer is a pager; extra footer content belongs in
|
|
65
65
|
* your own layout.
|
|
66
66
|
*
|
|
67
67
|
* Row totals come from `table.getRowCount()`, which prefers `options.rowCount`
|
|
68
|
-
*
|
|
68
|
+
* - so a server-paged grid shows the server's total without changes here.
|
|
69
69
|
*/
|
|
70
70
|
export function TMDataGridFooter({
|
|
71
71
|
pageSizeOptions = [10, 25, 50, 100],
|
|
@@ -84,7 +84,7 @@ export function TMDataGridFooter({
|
|
|
84
84
|
);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// Grouping suspends paging
|
|
87
|
+
// Grouping suspends paging - see isPagingActive. The pager is greyed out
|
|
88
88
|
// rather than dropped, so that a footer going quiet reads as a state the grid
|
|
89
89
|
// is in rather than as something that broke.
|
|
90
90
|
const paging = isPagingActive(table, features);
|
|
@@ -114,7 +114,13 @@ export function TMDataGridFooter({
|
|
|
114
114
|
data-paging-suspended={!paging}
|
|
115
115
|
>
|
|
116
116
|
<Group gap="xs" wrap="nowrap">
|
|
117
|
-
|
|
117
|
+
{/* `span`, here and in every other label the grid renders: Mantine's
|
|
118
|
+
Text is a `<p>` by default, and a grid dropped into prose - a
|
|
119
|
+
docs page, a CMS body, Mantine's own Typography - picks up that
|
|
120
|
+
context's `p` margins. A bottom margin on a flex item shifts its
|
|
121
|
+
border box up off centre, which is how the pager came to sit a
|
|
122
|
+
half-line below its own label. Chrome labels are not prose. */}
|
|
123
|
+
<Text span size={controlSize} c="dimmed">
|
|
118
124
|
{labels.rowsPerPage}
|
|
119
125
|
</Text>
|
|
120
126
|
<Select
|
|
@@ -133,11 +139,11 @@ export function TMDataGridFooter({
|
|
|
133
139
|
/>
|
|
134
140
|
</Group>
|
|
135
141
|
|
|
136
|
-
<Text size={controlSize} c="dimmed" data-dg-part="page-range">
|
|
142
|
+
<Text span size={controlSize} c="dimmed" data-dg-part="page-range">
|
|
137
143
|
{paging
|
|
138
144
|
? labels.pageRange({ from, to, total })
|
|
139
145
|
: // Not a range: nothing is being sliced, so a range would be a lie.
|
|
140
|
-
// Counted before grouping too
|
|
146
|
+
// Counted before grouping too - `getRowCount()` would be counting
|
|
141
147
|
// the tree, so a collapsed grid would claim to hold eight rows.
|
|
142
148
|
labels.groupedAllRows(table.getFilteredRowModel().rows.length)}
|
|
143
149
|
</Text>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
font-weight: 600;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/* Never squeezed out by a long group value
|
|
42
|
+
/* Never squeezed out by a long group value - the count is the part that tells
|
|
43
43
|
you whether the group is worth opening. */
|
|
44
44
|
.groupCount {
|
|
45
45
|
flex: 0 0 auto;
|
|
@@ -17,7 +17,7 @@ export const GROUP_COLUMN_ID = "__group__";
|
|
|
17
17
|
/** Indent added per level of nesting, in px. */
|
|
18
18
|
const INDENT_STEP = 16;
|
|
19
19
|
|
|
20
|
-
/** Shown for a group whose value is empty
|
|
20
|
+
/** Shown for a group whose value is empty - `String(null)` would read as "null". */
|
|
21
21
|
const BLANK_GROUP_LABEL = "(Blank)";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -42,7 +42,7 @@ export function formatGroupValue(
|
|
|
42
42
|
* The tree cell: chevron, group value and leaf count, indented by depth.
|
|
43
43
|
*
|
|
44
44
|
* Expansion is read through a subscription rather than from `row.getIsExpanded()`
|
|
45
|
-
* in the component body for the same reason the select checkbox does it
|
|
45
|
+
* in the component body for the same reason the select checkbox does it - the
|
|
46
46
|
* `row` identity survives an expand, so the React Compiler would cache the call
|
|
47
47
|
* along with it and the chevron would never turn. See TMDataGridSelectColumn.
|
|
48
48
|
*/
|
|
@@ -119,14 +119,14 @@ function GroupHeader<TData extends RowData>({
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* The generated tree column, prepended whenever grouping is enabled and hidden
|
|
122
|
-
* again while `grouping` is empty
|
|
122
|
+
* again while `grouping` is empty - see the visibility effect in
|
|
123
123
|
* `useTMDataGrid`.
|
|
124
124
|
*
|
|
125
125
|
* It exists because TanStack ships no auto group column: `groupedColumnMode:
|
|
126
126
|
* "remove"` takes the grouped column out of the grid, so something has to hold
|
|
127
127
|
* the tree. Modelled on the checkbox column, which is generated the same way.
|
|
128
128
|
*
|
|
129
|
-
* Not groupable itself, and nothing had to be written to make that true
|
|
129
|
+
* Not groupable itself, and nothing had to be written to make that true -
|
|
130
130
|
* `column.getCanGroup()` requires an `accessorFn`, which a display column has
|
|
131
131
|
* no reason to have.
|
|
132
132
|
*/
|
|
@@ -152,7 +152,7 @@ export function createGroupColumn<TData extends RowData>(
|
|
|
152
152
|
enablePinning: false,
|
|
153
153
|
cell: ({ row }) => <GroupCell row={row} />,
|
|
154
154
|
// A group row has subRows, so every cell on it that is not the grouped
|
|
155
|
-
// column reports `getIsAggregated()`
|
|
155
|
+
// column reports `getIsAggregated()` - this lane included. Without an
|
|
156
156
|
// `aggregatedCell` the body would take that as "nothing to summarise" and
|
|
157
157
|
// render the tree lane blank on exactly the rows it exists for.
|
|
158
158
|
aggregatedCell: ({ row }) => <GroupCell row={row} />,
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
justify-content: center;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/* Same as the body cells: a control lane's header
|
|
25
|
-
expand-all chevron
|
|
24
|
+
/* Same as the body cells: a control lane's header - the select-all box, the
|
|
25
|
+
expand-all chevron - sits in a fixed 36px track, which the text padding would
|
|
26
26
|
squeeze it out of at the larger sizes. */
|
|
27
27
|
.headerCell[data-control-column="true"] {
|
|
28
28
|
padding: 0;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
/* A filtered column reads as active: its rows are a subset, and nothing else in
|
|
32
32
|
the header says so once the filter panel is closed. A sorted one is not
|
|
33
|
-
tinted
|
|
33
|
+
tinted - the arrow, pinned visible for as long as the sort lasts, already
|
|
34
34
|
says which column sorts and which way. */
|
|
35
35
|
.headerCellFiltered .headerTitle {
|
|
36
36
|
color: var(--mantine-primary-color-filled);
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
margin-right: auto;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/* Idle actions are removed from layout, not just faded
|
|
74
|
+
/* Idle actions are removed from layout, not just faded - reserving their width
|
|
75
75
|
would truncate header text that otherwise fits. Indicators that must stay
|
|
76
76
|
visible (active sort, active filter, open menu) set data-pinned-visible. */
|
|
77
77
|
.headerAction {
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
opacity: 1;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
/* Sort priority under multi-sort
|
|
101
|
+
/* Sort priority under multi-sort - "1", "2", … beside the arrow. Only rendered
|
|
102
102
|
while more than one column sorts, so a single sort stays as it always was. */
|
|
103
103
|
.sortIndex {
|
|
104
104
|
font-size: 10px;
|
|
@@ -109,8 +109,8 @@
|
|
|
109
109
|
text-align: center;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/* Column reordering. The dragged header stays in place and dims
|
|
113
|
-
already carries a snapshot of it under the pointer
|
|
112
|
+
/* Column reordering. The dragged header stays in place and dims - the browser
|
|
113
|
+
already carries a snapshot of it under the pointer - while the header being
|
|
114
114
|
dragged over draws a bar on the edge the column will land against. */
|
|
115
115
|
.headerCellDragging {
|
|
116
116
|
opacity: 0.4;
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
touch-action: none;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
/* The divider is a hover affordance, not permanent chrome
|
|
157
|
+
/* The divider is a hover affordance, not permanent chrome - it only appears on
|
|
158
158
|
the header cell you are pointing at, or on the column being resized. */
|
|
159
159
|
.columnSeparator::after {
|
|
160
160
|
content: "";
|
|
@@ -67,7 +67,7 @@ export function TMDataGridHeaderCell({
|
|
|
67
67
|
const [contextMenuOpened, setContextMenuOpened] = useState(false);
|
|
68
68
|
const [dropSide, setDropSide] = useState<TMDataGridDropSide | null>(null);
|
|
69
69
|
const cellRef = useRef<HTMLDivElement>(null);
|
|
70
|
-
// A resize drag ends with a click that bubbles to the header
|
|
70
|
+
// A resize drag ends with a click that bubbles to the header - without this
|
|
71
71
|
// every resize would also toggle the column's sort order.
|
|
72
72
|
const suppressSortRef = useRef(false);
|
|
73
73
|
|
|
@@ -96,7 +96,7 @@ export function TMDataGridHeaderCell({
|
|
|
96
96
|
const sortDirection = sorting.find((sort) => sort.id === column.id)?.desc;
|
|
97
97
|
const isSorted = sortDirection !== undefined;
|
|
98
98
|
// 1-based position in a multi-sort, shown only while more than one column
|
|
99
|
-
// sorts
|
|
99
|
+
// sorts - under a single sort the arrow already says everything.
|
|
100
100
|
const sortIndex =
|
|
101
101
|
isSorted && sorting.length > 1
|
|
102
102
|
? sorting.findIndex((sort) => sort.id === column.id) + 1
|
|
@@ -184,7 +184,7 @@ export function TMDataGridHeaderCell({
|
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* Sizes the column to its widest mounted content. The container is found
|
|
187
|
-
* from the cell rather than passed down
|
|
187
|
+
* from the cell rather than passed down - the header always renders inside
|
|
188
188
|
* the grid's scroll container, and that is the subtree autosize measures.
|
|
189
189
|
*/
|
|
190
190
|
function autosize() {
|
|
@@ -197,7 +197,7 @@ export function TMDataGridHeaderCell({
|
|
|
197
197
|
/**
|
|
198
198
|
* Pinned columns are positioned with `getStart()` / `getAfter()`, which sum
|
|
199
199
|
* `column.getSize()`. Fluid (`fr`) columns don't report their rendered width
|
|
200
|
-
* there, so freeze the measured width into `columnSizing` as we pin
|
|
200
|
+
* there, so freeze the measured width into `columnSizing` as we pin - that
|
|
201
201
|
* keeps the column looking the same and keeps the offsets exact.
|
|
202
202
|
*/
|
|
203
203
|
function setPinned(position: "left" | "right" | false) {
|
|
@@ -265,7 +265,7 @@ export function TMDataGridHeaderCell({
|
|
|
265
265
|
}
|
|
266
266
|
if (isGroupingActive) {
|
|
267
267
|
// Under the default `groupedColumnMode: "remove"` a grouped column is taken
|
|
268
|
-
// out of the grid, so its own header
|
|
268
|
+
// out of the grid, so its own header (and the Ungroup item on it) is no
|
|
269
269
|
// longer reachable. The tree column that replaced it carries them instead,
|
|
270
270
|
// which is also where someone would look for them.
|
|
271
271
|
if (column.id === GROUP_COLUMN_ID) {
|
|
@@ -284,7 +284,7 @@ export function TMDataGridHeaderCell({
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
// Groups only, though `expanded` also holds which rows have their detail
|
|
287
|
-
// panel open
|
|
287
|
+
// panel open - `toggleAllRowsExpanded` would open every one of them from an
|
|
288
288
|
// item that says "groups". See resolveExpandAll.
|
|
289
289
|
const expandAllGroups = (expand: boolean) =>
|
|
290
290
|
table.setExpanded(
|
|
@@ -349,7 +349,7 @@ export function TMDataGridHeaderCell({
|
|
|
349
349
|
menuItems.push(<Menu.Divider key="pin-divider" />);
|
|
350
350
|
}
|
|
351
351
|
if (canReorder) {
|
|
352
|
-
// The keyboard path to reordering, and the discoverable one
|
|
352
|
+
// The keyboard path to reordering, and the discoverable one - a header you
|
|
353
353
|
// can drag looks no different from one you cannot.
|
|
354
354
|
const previous = getStepTargetColumn({
|
|
355
355
|
table,
|
|
@@ -421,12 +421,12 @@ export function TMDataGridHeaderCell({
|
|
|
421
421
|
</Menu.Item>,
|
|
422
422
|
);
|
|
423
423
|
}
|
|
424
|
-
// A trailing divider reads as a dropped item
|
|
424
|
+
// A trailing divider reads as a dropped item - drop the divider instead.
|
|
425
425
|
while (menuItems.length > 0 && isDivider(menuItems.at(-1))) {
|
|
426
426
|
menuItems.pop();
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
// One gate for both ways into the menu
|
|
429
|
+
// One gate for both ways into the menu - the button and the right-click -
|
|
430
430
|
// so the two can never disagree about which headers have one. A control
|
|
431
431
|
// lane's header is itself a control (select-all, expand-all), so it carries
|
|
432
432
|
// no column menu, and a right-click there falls through to the browser's.
|
|
@@ -436,7 +436,7 @@ export function TMDataGridHeaderCell({
|
|
|
436
436
|
classes.headerCell,
|
|
437
437
|
canSort ? classes.headerCellSortable : "",
|
|
438
438
|
// Only the filter tints the title. `data-active` below stays
|
|
439
|
-
// sorted-or-filtered
|
|
439
|
+
// sorted-or-filtered - it is the queryable state, not the styling hook.
|
|
440
440
|
isFiltered ? classes.headerCellFiltered : "",
|
|
441
441
|
isDragged ? classes.headerCellDragging : "",
|
|
442
442
|
dropSide === "before" ? classes.headerCellDropBefore : "",
|
|
@@ -615,7 +615,7 @@ export function TMDataGridHeaderCell({
|
|
|
615
615
|
)}
|
|
616
616
|
|
|
617
617
|
{/* A system lane has no separator at all: it cannot be resized, and the
|
|
618
|
-
hover line reads as a handle that does nothing
|
|
618
|
+
hover line reads as a handle that does nothing - the one place the
|
|
619
619
|
separator stops being a divider and starts being a lie. */}
|
|
620
620
|
{!isControlColumn(column.id) && (
|
|
621
621
|
<div
|
|
@@ -659,7 +659,7 @@ export function TMDataGridHeaderCell({
|
|
|
659
659
|
// A second Menu for the same items, rather than one Menu serving both ways
|
|
660
660
|
// in: the button's dropdown hangs off the button, while a context menu
|
|
661
661
|
// belongs at the pointer, and a Mantine Menu has one target. `menuItems` is
|
|
662
|
-
// built once and rendered by whichever dropdown is open
|
|
662
|
+
// built once and rendered by whichever dropdown is open (never both), so
|
|
663
663
|
// the two can't drift apart.
|
|
664
664
|
//
|
|
665
665
|
// The per-header Popover this adds is not the cost the body pays for one per
|
|
@@ -4,14 +4,14 @@ import type { TMDataGridFeatures } from "../useTMDataGrid";
|
|
|
4
4
|
export const ROW_NUMBER_COLUMN_ID = "__rowNumber__";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The generated row-number gutter, prepended under `enableRowNumbers`
|
|
7
|
+
* The generated row-number gutter, prepended under `enableRowNumbers` -
|
|
8
8
|
* outermost left, before even the checkbox lane, the way a spreadsheet keeps
|
|
9
9
|
* its gutter outside everything.
|
|
10
10
|
*
|
|
11
11
|
* The def renders nothing itself: a cell cannot know its display position,
|
|
12
|
-
* so the body computes the numbers
|
|
12
|
+
* so the body computes the numbers - one pass over the view, group rows
|
|
13
13
|
* skipped (they are headings over the rows being counted), continuing across
|
|
14
|
-
* pages
|
|
14
|
+
* pages - and substitutes them in. See `TMDataGridTable`.
|
|
15
15
|
*
|
|
16
16
|
* A system lane: fixed width, no menu, no resize, never exported, and its
|
|
17
17
|
* visibility follows the option rather than the columns panel.
|
|
@@ -37,7 +37,7 @@ export function createRowNumberColumn<TData extends RowData>(
|
|
|
37
37
|
enablePinning: false,
|
|
38
38
|
enableHiding: false,
|
|
39
39
|
header: () => "#",
|
|
40
|
-
// The body substitutes the number; a row without one
|
|
40
|
+
// The body substitutes the number; a row without one - a group row -
|
|
41
41
|
// falls through to this.
|
|
42
42
|
cell: () => null,
|
|
43
43
|
};
|