@jielga/tmdatagrid 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -2
- package/dist/index.d.ts +792 -327
- package/dist/index.js +1581 -892
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +15 -7
- package/skills/columns/SKILL.md +8 -7
- package/skills/features/SKILL.md +104 -16
- package/skills/getting-started/SKILL.md +78 -20
- package/skills/options/SKILL.md +12 -7
- package/skills/server-side/SKILL.md +5 -2
- package/src/tmdatagrid/TMDataGridContext.ts +1 -1
- package/src/tmdatagrid/components/TMDataGrid.module.css +73 -0
- package/src/tmdatagrid/{TMDataGrid.tsx → components/TMDataGrid.tsx} +15 -4
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.module.css +25 -0
- package/src/tmdatagrid/{TMDataGridColumnsPanel.tsx → components/TMDataGridColumnsPanel.tsx} +10 -4
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +35 -0
- package/src/tmdatagrid/{TMDataGridFilterPanel.tsx → components/TMDataGridFilterPanel.tsx} +91 -14
- package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +35 -0
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +135 -0
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +18 -0
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +171 -0
- package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +48 -0
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +147 -0
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +159 -0
- package/src/tmdatagrid/{TMDataGridHeaderCell.tsx → components/TMDataGridHeaderCell.tsx} +99 -14
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +177 -0
- package/src/tmdatagrid/components/TMDataGridTable.module.css +137 -0
- package/src/tmdatagrid/components/TMDataGridTable.tsx +720 -0
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +12 -0
- package/src/tmdatagrid/{TMDataGridToolbar.tsx → components/TMDataGridToolbar.tsx} +8 -5
- package/src/tmdatagrid/{icons.ts → components/icons.ts} +5 -0
- package/src/tmdatagrid/components/sticky.module.css +45 -0
- package/src/tmdatagrid/core/capabilities.ts +215 -0
- package/src/tmdatagrid/{columnOrdering.ts → core/columnOrdering.ts} +2 -2
- package/src/tmdatagrid/{columnUtils.ts → core/columnUtils.ts} +1 -1
- package/src/tmdatagrid/{filterOperators.ts → core/filterOperators.ts} +25 -0
- package/src/tmdatagrid/core/grouping.ts +21 -0
- package/src/tmdatagrid/{persistence.ts → core/persistence.ts} +92 -2
- package/src/tmdatagrid/core/rowSelection.ts +202 -0
- package/src/tmdatagrid/index.ts +39 -9
- package/src/tmdatagrid/useTMDataGrid.tsx +318 -66
- package/src/tmdatagrid/TMDataGrid.module.css +0 -410
- package/src/tmdatagrid/TMDataGridFooter.tsx +0 -74
- package/src/tmdatagrid/TMDataGridTable.tsx +0 -297
- package/src/tmdatagrid/capabilities.ts +0 -117
- /package/src/tmdatagrid/{sizes.ts → core/sizes.ts} +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Row, RowData, RowSelectionState } from "@tanstack/react-table";
|
|
2
|
+
import type { TMDataGridFeatureFlags } from "./capabilities";
|
|
3
|
+
import { getGroupDataRows } from "./grouping";
|
|
4
|
+
import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rows in the order they are displayed, which is the order a shift-click range
|
|
8
|
+
* is measured over — so a range follows the active sort and skips filtered-out
|
|
9
|
+
* rows. Under pagination it is the current page, so a range cannot cross a page
|
|
10
|
+
* boundary.
|
|
11
|
+
*
|
|
12
|
+
* Once a column is grouped this includes the group rows, and only the leaves of
|
|
13
|
+
* groups that are open — and paging is suspended, so it is the whole tree. That
|
|
14
|
+
* is the right list for a range: it is what the user can see, and a range that
|
|
15
|
+
* swept collapsed rows would select things off screen. What a group row in the
|
|
16
|
+
* range contributes is decided by {@link getSelectableRowIds}.
|
|
17
|
+
*/
|
|
18
|
+
export function getDisplayedRows<TData extends RowData>(
|
|
19
|
+
table: TMDataGridTable<TData>,
|
|
20
|
+
features: TMDataGridFeatureFlags,
|
|
21
|
+
): Array<Row<TMDataGridFeatures, TData>> {
|
|
22
|
+
return isPagingActive(table, features)
|
|
23
|
+
? table.getPaginatedRowModel().rows
|
|
24
|
+
: table.getPrePaginatedRowModel().rows;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether the built-in pager is actually slicing anything.
|
|
29
|
+
*
|
|
30
|
+
* Grouping suspends it. A page can only count one kind of thing, and once the
|
|
31
|
+
* rows are a tree neither answer works: counting every row splits a group
|
|
32
|
+
* across a page boundary and strands the rest of the tree on pages the user has
|
|
33
|
+
* to go looking for, and counting only the top-level rows quietly redefines
|
|
34
|
+
* "rows per page" as groups per page, so a page of 25 can hold thousands of
|
|
35
|
+
* rows. Rather than pick, the grid renders the whole tree and leans on the
|
|
36
|
+
* virtualizer — which is its default mode anyway, pagination being opt-in.
|
|
37
|
+
*
|
|
38
|
+
* `TMDataGrid.Footer` reads this too, and greys the pager out rather than
|
|
39
|
+
* hiding it, so the pager going quiet is visible instead of mysterious.
|
|
40
|
+
*/
|
|
41
|
+
export function isPagingActive<TData extends RowData>(
|
|
42
|
+
table: TMDataGridTable<TData>,
|
|
43
|
+
features: TMDataGridFeatureFlags,
|
|
44
|
+
): boolean {
|
|
45
|
+
return features.pagination && table.store.state.grouping.length === 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Which modifiers were held. Named for what they mean rather than for the keys,
|
|
50
|
+
* because the keys differ per platform — `toggle` is Ctrl on Windows/Linux and
|
|
51
|
+
* Cmd on macOS.
|
|
52
|
+
*/
|
|
53
|
+
export type TMDataGridRowClickModifiers = {
|
|
54
|
+
toggle: boolean;
|
|
55
|
+
extend: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ResolveRowSelectionClickArgs<TData extends RowData> = {
|
|
59
|
+
/** Displayed order — see {@link getDisplayedRows}. */
|
|
60
|
+
rows: ReadonlyArray<Row<TMDataGridFeatures, TData>>;
|
|
61
|
+
rowId: string;
|
|
62
|
+
/** The pivot a shift-click extends from. */
|
|
63
|
+
anchorRowId: string | null;
|
|
64
|
+
modifiers: TMDataGridRowClickModifiers;
|
|
65
|
+
selection: RowSelectionState;
|
|
66
|
+
/**
|
|
67
|
+
* Whether this gesture is allowed to clear rows it did not touch.
|
|
68
|
+
*
|
|
69
|
+
* `true` for a bare row click, where replacing is the whole point. `false` for
|
|
70
|
+
* a checkbox, which is only ever additive — ticking one box has never cleared
|
|
71
|
+
* the others, and shift-clicking one adds the range rather than becoming it.
|
|
72
|
+
*/
|
|
73
|
+
canReplaceSelection: boolean;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type ResolvedRowSelection = {
|
|
77
|
+
selection: RowSelectionState;
|
|
78
|
+
anchorRowId: string | null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The row ids one gesture on this row selects.
|
|
83
|
+
*
|
|
84
|
+
* A group row is never selected itself: it holds no record, and TanStack
|
|
85
|
+
* derives its checked state from its descendants. So ticking a group means
|
|
86
|
+
* ticking every selectable leaf under it, which is also what makes the group's
|
|
87
|
+
* own box light up.
|
|
88
|
+
*
|
|
89
|
+
* Leaves rather than direct children, so one click on a top-level group takes
|
|
90
|
+
* the whole subtree however deeply it nests.
|
|
91
|
+
*/
|
|
92
|
+
export function getSelectableRowIds<TData extends RowData>(
|
|
93
|
+
row: Row<TMDataGridFeatures, TData>,
|
|
94
|
+
): Array<string> {
|
|
95
|
+
return getGroupDataRows(row)
|
|
96
|
+
.filter((dataRow) => dataRow.getCanSelect())
|
|
97
|
+
.map((dataRow) => dataRow.id);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Turns one click into the next selection, following the convention every
|
|
102
|
+
* desktop list uses:
|
|
103
|
+
*
|
|
104
|
+
* | Gesture | Effect |
|
|
105
|
+
* | ------- | ------ |
|
|
106
|
+
* | Click | selection becomes only this row |
|
|
107
|
+
* | Ctrl/Cmd + click | toggle this row, leave the rest |
|
|
108
|
+
* | Shift + click | selection becomes the range anchor → this row |
|
|
109
|
+
* | Ctrl/Cmd + Shift + click | add that range to the selection |
|
|
110
|
+
*
|
|
111
|
+
* The anchor is a *pivot*, not "the last row touched": only a plain or
|
|
112
|
+
* Ctrl-click moves it, which is what lets a run of shift-clicks grow and shrink
|
|
113
|
+
* the same range from a fixed point.
|
|
114
|
+
*
|
|
115
|
+
* Selection state is rebuilt here and handed to `setRowSelection` wholesale
|
|
116
|
+
* rather than going through `row.toggleSelected()` per row, which would publish
|
|
117
|
+
* one state update each. That skips TanStack's sub-row cascade, so the cascade
|
|
118
|
+
* is done here instead: every row in the gesture is expanded through
|
|
119
|
+
* {@link getSelectableRowIds} before it reaches the map. Under grouping that is
|
|
120
|
+
* what makes one tick on a group select all of it.
|
|
121
|
+
*/
|
|
122
|
+
export function resolveRowSelectionClick<TData extends RowData>({
|
|
123
|
+
rows,
|
|
124
|
+
rowId,
|
|
125
|
+
anchorRowId,
|
|
126
|
+
modifiers,
|
|
127
|
+
selection,
|
|
128
|
+
canReplaceSelection,
|
|
129
|
+
}: ResolveRowSelectionClickArgs<TData>): ResolvedRowSelection {
|
|
130
|
+
const index = rows.findIndex((row) => row.id === rowId);
|
|
131
|
+
const clickedRow = rows[index];
|
|
132
|
+
// Clicked row is not in the displayed set — nothing sensible to resolve.
|
|
133
|
+
if (index === -1 || clickedRow === undefined) return { selection, anchorRowId };
|
|
134
|
+
|
|
135
|
+
const targets = getSelectableRowIds(clickedRow);
|
|
136
|
+
// Nothing selectable under this gesture: a row the predicate refuses, or a
|
|
137
|
+
// group whose every leaf refuses.
|
|
138
|
+
if (targets.length === 0) return { selection, anchorRowId };
|
|
139
|
+
|
|
140
|
+
// `enableMultiRowSelection: false`, or the per-row predicate saying no: one
|
|
141
|
+
// row at a time, so the modifiers have nothing to build on and every gesture
|
|
142
|
+
// collapses to a single id.
|
|
143
|
+
//
|
|
144
|
+
// This has to be enforced here. Rebuilding the map and handing it to
|
|
145
|
+
// `setRowSelection` skips `mutateRowIsSelected`, which is where TanStack
|
|
146
|
+
// clears the other rows — so without this, Ctrl and Shift would quietly
|
|
147
|
+
// produce the multi-selection the option ruled out.
|
|
148
|
+
if (!clickedRow.getCanMultiSelect()) {
|
|
149
|
+
// A group stands for several rows, which single-select cannot express. The
|
|
150
|
+
// checkbox column drops the box on those rows for the same reason.
|
|
151
|
+
const only = targets.length === 1 ? targets[0] : undefined;
|
|
152
|
+
if (only === undefined) return { selection, anchorRowId };
|
|
153
|
+
// A checkbox must still be able to untick the one selected row; a row click
|
|
154
|
+
// only ever sets, so re-clicking it cannot empty the selection.
|
|
155
|
+
const untick = !canReplaceSelection && selection[only] === true;
|
|
156
|
+
return {
|
|
157
|
+
selection: untick ? {} : { [only]: true },
|
|
158
|
+
anchorRowId: rowId,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const anchorIndex =
|
|
163
|
+
anchorRowId === null
|
|
164
|
+
? -1
|
|
165
|
+
: rows.findIndex((row) => row.id === anchorRowId);
|
|
166
|
+
|
|
167
|
+
// A range needs a pivot that is still on screen. When the anchor has been
|
|
168
|
+
// filtered or paged away, fall through and treat this as a fresh click.
|
|
169
|
+
if (modifiers.extend && anchorIndex !== -1) {
|
|
170
|
+
const from = Math.min(anchorIndex, index);
|
|
171
|
+
const to = Math.max(anchorIndex, index);
|
|
172
|
+
// Ctrl+Shift adds to what is already selected; Shift alone becomes the
|
|
173
|
+
// range — unless the gesture may not replace, in which case it also adds.
|
|
174
|
+
const next: RowSelectionState =
|
|
175
|
+
modifiers.toggle || !canReplaceSelection ? { ...selection } : {};
|
|
176
|
+
for (const row of rows.slice(from, to + 1)) {
|
|
177
|
+
// A group row inside the range brings its whole subtree, including leaves
|
|
178
|
+
// that are collapsed out of `rows` — sweeping past a closed group selects
|
|
179
|
+
// it entire, which is the only reading that matches what it displays.
|
|
180
|
+
for (const id of getSelectableRowIds(row)) next[id] = true;
|
|
181
|
+
}
|
|
182
|
+
// The pivot deliberately stays put.
|
|
183
|
+
return { selection: next, anchorRowId };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (modifiers.toggle || !canReplaceSelection) {
|
|
187
|
+
const next: RowSelectionState = { ...selection };
|
|
188
|
+
// Asked of the targets rather than of `rowId`: a group row carries no id in
|
|
189
|
+
// the map, so it counts as selected exactly when all of its leaves do —
|
|
190
|
+
// which is also when its checkbox shows a tick rather than a dash.
|
|
191
|
+
const allSelected = targets.every((id) => selection[id] === true);
|
|
192
|
+
for (const id of targets) {
|
|
193
|
+
if (allSelected) delete next[id];
|
|
194
|
+
else next[id] = true;
|
|
195
|
+
}
|
|
196
|
+
return { selection: next, anchorRowId: rowId };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const next: RowSelectionState = {};
|
|
200
|
+
for (const id of targets) next[id] = true;
|
|
201
|
+
return { selection: next, anchorRowId: rowId };
|
|
202
|
+
}
|
package/src/tmdatagrid/index.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
export { TMDataGrid, type TMDataGridProps } from "./TMDataGrid";
|
|
1
|
+
export { TMDataGrid, type TMDataGridProps } from "./components/TMDataGrid";
|
|
2
2
|
export {
|
|
3
3
|
useTMDataGridContext,
|
|
4
4
|
type TMDataGridContextValue,
|
|
5
5
|
type TMDataGridRowData,
|
|
6
6
|
} from "./TMDataGridContext";
|
|
7
|
+
export { SELECT_COLUMN_ID } from "./components/TMDataGridSelectColumn";
|
|
8
|
+
export {
|
|
9
|
+
formatGroupValue,
|
|
10
|
+
GROUP_COLUMN_ID,
|
|
11
|
+
} from "./components/TMDataGridGroupColumn";
|
|
7
12
|
export {
|
|
8
13
|
createTMDataGridColumnHelper,
|
|
9
14
|
openColumnFilter,
|
|
10
|
-
SELECT_COLUMN_ID,
|
|
11
15
|
tmDataGridFeatures,
|
|
12
16
|
type TMDataGridApi,
|
|
13
17
|
type TMDataGridColumnMeta,
|
|
@@ -20,18 +24,24 @@ export {
|
|
|
20
24
|
useTMDataGrid,
|
|
21
25
|
type UseTMDataGridOptions,
|
|
22
26
|
} from "./useTMDataGrid";
|
|
27
|
+
export {
|
|
28
|
+
TMDataGridFilterPills,
|
|
29
|
+
type TMDataGridFilterPillsProps,
|
|
30
|
+
} from "./components/TMDataGridFilterPills";
|
|
23
31
|
export {
|
|
24
32
|
type TMDataGridColumnType,
|
|
25
33
|
type TMDataGridFilterOperator,
|
|
26
34
|
type TMDataGridFilterValue,
|
|
27
35
|
FILTER_OPERATOR_LABELS,
|
|
36
|
+
formatFilterLabel,
|
|
28
37
|
isFilterActive,
|
|
29
|
-
} from "./filterOperators";
|
|
38
|
+
} from "./core/filterOperators";
|
|
30
39
|
export {
|
|
31
40
|
getColumnLabel,
|
|
32
41
|
getColumnType,
|
|
33
42
|
isColumnReorderable,
|
|
34
|
-
} from "./columnUtils";
|
|
43
|
+
} from "./core/columnUtils";
|
|
44
|
+
export { getGroupDataRows } from "./core/grouping";
|
|
35
45
|
export {
|
|
36
46
|
getColumnRegion,
|
|
37
47
|
getStepTargetColumn,
|
|
@@ -41,7 +51,7 @@ export {
|
|
|
41
51
|
type MoveColumnArgs,
|
|
42
52
|
type TMDataGridColumnRegion,
|
|
43
53
|
type TMDataGridDropSide,
|
|
44
|
-
} from "./columnOrdering";
|
|
54
|
+
} from "./core/columnOrdering";
|
|
45
55
|
export {
|
|
46
56
|
getColumnCapabilities,
|
|
47
57
|
getGridCapabilities,
|
|
@@ -49,13 +59,23 @@ export {
|
|
|
49
59
|
type TMDataGridCapabilities,
|
|
50
60
|
type TMDataGridColumnCapabilities,
|
|
51
61
|
type TMDataGridFeatureFlags,
|
|
52
|
-
|
|
62
|
+
type TMDataGridSelectionMode,
|
|
63
|
+
} from "./core/capabilities";
|
|
64
|
+
export {
|
|
65
|
+
getDisplayedRows,
|
|
66
|
+
getSelectableRowIds,
|
|
67
|
+
isPagingActive,
|
|
68
|
+
resolveRowSelectionClick,
|
|
69
|
+
type ResolvedRowSelection,
|
|
70
|
+
type ResolveRowSelectionClickArgs,
|
|
71
|
+
type TMDataGridRowClickModifiers,
|
|
72
|
+
} from "./core/rowSelection";
|
|
53
73
|
export {
|
|
54
74
|
DEFAULT_TMDATAGRID_SIZE,
|
|
55
75
|
SIZE_CONTROL_SIZE,
|
|
56
76
|
SIZE_ROW_HEIGHT,
|
|
57
77
|
type TMDataGridSize,
|
|
58
|
-
} from "./sizes";
|
|
78
|
+
} from "./core/sizes";
|
|
59
79
|
export {
|
|
60
80
|
DATA_STATE_SLICES,
|
|
61
81
|
SETTINGS_STATE_SLICES,
|
|
@@ -64,5 +84,15 @@ export {
|
|
|
64
84
|
type TMDataGridPersistKey,
|
|
65
85
|
type TMDataGridSettingsSlice,
|
|
66
86
|
type TMDataGridStorageMode,
|
|
67
|
-
} from "./persistence";
|
|
68
|
-
export type {
|
|
87
|
+
} from "./core/persistence";
|
|
88
|
+
export type {
|
|
89
|
+
TMDataGridColumnLayout,
|
|
90
|
+
TMDataGridRowContextMenu,
|
|
91
|
+
TMDataGridRowContextMenuArgs,
|
|
92
|
+
TMDataGridTableProps,
|
|
93
|
+
} from "./components/TMDataGridTable";
|
|
94
|
+
export {
|
|
95
|
+
getTMDataGridPaginationApi,
|
|
96
|
+
type TMDataGridFooterProps,
|
|
97
|
+
type TMDataGridPaginationApi,
|
|
98
|
+
} from "./components/TMDataGridFooter";
|