@jielga/tmdatagrid 0.5.0 → 1.0.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 +25 -2
- package/dist/index.d.ts +856 -14
- package/dist/index.js +3370 -1546
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.layer.css +1 -0
- package/package.json +12 -2
- package/skills/columns/SKILL.md +1 -1
- package/skills/features/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +1 -1
- package/skills/options/SKILL.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +198 -0
- package/src/tmdatagrid/components/TMDataGrid.module.css +11 -0
- package/src/tmdatagrid/components/TMDataGrid.tsx +34 -0
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +256 -0
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +35 -12
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +3 -0
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +62 -0
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +237 -0
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +164 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +77 -62
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +6 -0
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +10 -3
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +2 -0
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +15 -2
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +21 -4
- package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +44 -0
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +2 -0
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +3 -0
- package/src/tmdatagrid/components/TMDataGridTable.module.css +206 -0
- package/src/tmdatagrid/components/TMDataGridTable.tsx +841 -57
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +12 -7
- package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +29 -0
- package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +57 -0
- package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +45 -0
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +38 -0
- package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +53 -0
- package/src/tmdatagrid/components/editors/TMDataGridStringEditor.tsx +31 -0
- package/src/tmdatagrid/components/editors/editorShared.ts +48 -0
- package/src/tmdatagrid/components/filters/DgAutocompleteFilter.tsx +46 -0
- package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +43 -0
- package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +52 -0
- package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +38 -0
- package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +122 -0
- package/src/tmdatagrid/components/icons.ts +4 -0
- package/src/tmdatagrid/core/autosize.ts +1 -1
- package/src/tmdatagrid/core/capabilities.ts +37 -1
- package/src/tmdatagrid/core/cellNavigation.ts +41 -0
- package/src/tmdatagrid/core/columnUtils.ts +31 -6
- package/src/tmdatagrid/core/editEngine.ts +857 -0
- package/src/tmdatagrid/core/filterControls.ts +45 -0
- package/src/tmdatagrid/core/filterOperators.ts +72 -4
- package/src/tmdatagrid/core/labels.ts +68 -1
- package/src/tmdatagrid/core/labelsSv.ts +24 -1
- package/src/tmdatagrid/core/matchHighlight.ts +96 -0
- package/src/tmdatagrid/core/persistence.ts +97 -3
- package/src/tmdatagrid/core/quickSearch.ts +144 -0
- package/src/tmdatagrid/core/rowPinning.ts +37 -0
- package/src/tmdatagrid/core/rowSelection.ts +10 -1
- package/src/tmdatagrid/index.ts +51 -0
- package/src/tmdatagrid/useTMDataGrid.tsx +456 -15
|
@@ -24,7 +24,11 @@ export {
|
|
|
24
24
|
IconIndentIncrease as GroupIcon,
|
|
25
25
|
IconArrowBarToLeft as PinLeftIcon,
|
|
26
26
|
IconArrowBarToRight as PinRightIcon,
|
|
27
|
+
IconArrowBackUp as RestoreIcon,
|
|
28
|
+
IconCheck as CheckIcon,
|
|
27
29
|
IconMenu2 as BurgerIcon,
|
|
30
|
+
IconTrash as TrashIcon,
|
|
31
|
+
IconPencil as PencilIcon,
|
|
28
32
|
IconPinnedOff as PinOffIcon,
|
|
29
33
|
IconSearch as SearchIcon,
|
|
30
34
|
IconX as CloseIcon,
|
|
@@ -72,7 +72,7 @@ export function measureColumnContentWidth({
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const title = container.querySelector<HTMLElement>(
|
|
75
|
-
`[data-
|
|
75
|
+
`[data-column-id="${CSS.escape(columnId)}"] [data-dg-header-title]`,
|
|
76
76
|
);
|
|
77
77
|
if (title) {
|
|
78
78
|
const header = title.closest<HTMLElement>('[role="columnheader"]');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Column, RowData } from "@tanstack/react-table";
|
|
2
2
|
import type { TMDataGridRowData } from "../TMDataGridContext";
|
|
3
3
|
import { isColumnReorderable } from "./columnUtils";
|
|
4
|
+
import type { TMDataGridEditMode } from "./editEngine";
|
|
4
5
|
import type {
|
|
5
6
|
TMDataGridFeatures,
|
|
6
7
|
TMDataGridTable,
|
|
@@ -117,6 +118,25 @@ export type TMDataGridFeatureFlags = {
|
|
|
117
118
|
* its own grouping can still say `enableGrouping: true` to override.
|
|
118
119
|
*/
|
|
119
120
|
grouping: boolean;
|
|
121
|
+
/** Whether cells can be edited at all — `editMode` was set. */
|
|
122
|
+
editing: boolean;
|
|
123
|
+
/** The commit policy, or `null` while editing is off. */
|
|
124
|
+
editMode: TMDataGridEditMode | null;
|
|
125
|
+
/** The generated row-number gutter — `enableRowNumbers`. Off by default. */
|
|
126
|
+
rowNumbers: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Whether rows can be pinned to the top or bottom edge — `enableRowPinning`,
|
|
129
|
+
* boolean or per-row predicate. Off by default: pinning has no built-in
|
|
130
|
+
* gesture, so a grid that never calls `row.pin()` has nothing to gain from
|
|
131
|
+
* the body watching the state.
|
|
132
|
+
*/
|
|
133
|
+
rowPinning: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Whether cells highlight the matched text while a contains-family filter
|
|
136
|
+
* or the quick search is active — `enableMatchHighlighting`. Off by
|
|
137
|
+
* default, so a grid that has not asked pays nothing per cell.
|
|
138
|
+
*/
|
|
139
|
+
matchHighlighting: boolean;
|
|
120
140
|
};
|
|
121
141
|
|
|
122
142
|
export function readFeatureFlags<TData extends RowData>(
|
|
@@ -137,10 +157,18 @@ export function readFeatureFlags<TData extends RowData>(
|
|
|
137
157
|
| "enablePagination"
|
|
138
158
|
| "manualPagination"
|
|
139
159
|
| "enableGrouping"
|
|
160
|
+
| "editMode"
|
|
161
|
+
| "enableRowNumbers"
|
|
162
|
+
| "enableRowPinning"
|
|
163
|
+
| "enableMatchHighlighting"
|
|
140
164
|
>,
|
|
141
165
|
): TMDataGridFeatureFlags {
|
|
142
166
|
const selectionMode = options.selectionMode ?? "checkbox";
|
|
143
|
-
|
|
167
|
+
// Editing brings the cell cursor with it: Enter, F2 and type-to-edit all
|
|
168
|
+
// address "the focused cell", which only exists under cell selection. An
|
|
169
|
+
// explicit `cellSelection` still wins, `"range"` included.
|
|
170
|
+
const cellSelectionMode =
|
|
171
|
+
options.cellSelection ?? (options.editMode !== undefined ? "single" : "none");
|
|
144
172
|
// `"highlight"` is the master-detail mode: no selection to speak of, so the
|
|
145
173
|
// checkbox column and the click-to-select behaviour both fall away. Otherwise
|
|
146
174
|
// `enableRowSelection` still has the final say, including its predicate form.
|
|
@@ -174,6 +202,14 @@ export function readFeatureFlags<TData extends RowData>(
|
|
|
174
202
|
pagination:
|
|
175
203
|
options.enablePagination === true || options.manualPagination === true,
|
|
176
204
|
grouping: options.enableGrouping ?? options.manualPagination !== true,
|
|
205
|
+
editing: options.editMode !== undefined,
|
|
206
|
+
editMode: options.editMode ?? null,
|
|
207
|
+
rowNumbers: options.enableRowNumbers === true,
|
|
208
|
+
// A predicate counts as on — some rows may still pin.
|
|
209
|
+
rowPinning:
|
|
210
|
+
options.enableRowPinning === true ||
|
|
211
|
+
typeof options.enableRowPinning === "function",
|
|
212
|
+
matchHighlighting: options.enableMatchHighlighting === true,
|
|
177
213
|
};
|
|
178
214
|
}
|
|
179
215
|
|
|
@@ -93,6 +93,47 @@ export function resolveCellMove({
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
export type NextEditableCellArgs = {
|
|
97
|
+
from: TMDataGridCellCoords;
|
|
98
|
+
/** `1` is Tab, `-1` is Shift+Tab. */
|
|
99
|
+
direction: 1 | -1;
|
|
100
|
+
rowCount: number;
|
|
101
|
+
columnCount: number;
|
|
102
|
+
/** Whether the cell at these coordinates takes edits. */
|
|
103
|
+
isEditable: (coords: TMDataGridCellCoords) => boolean;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The next editable cell in reading order, wrapping to the next row — and
|
|
108
|
+
* from the last cell of the grid back to the first. `null` when no other
|
|
109
|
+
* cell is editable.
|
|
110
|
+
*
|
|
111
|
+
* Deliberately wrapping where {@link resolveCellMove} deliberately clamps:
|
|
112
|
+
* arrows are a coordinate space and clamp so the destination is predictable,
|
|
113
|
+
* but Tab mid-edit is the spreadsheet's "next field" — at the end of a row
|
|
114
|
+
* the next field is on the next row, which is a convention, not a surprise.
|
|
115
|
+
*/
|
|
116
|
+
export function getNextEditableCell({
|
|
117
|
+
from,
|
|
118
|
+
direction,
|
|
119
|
+
rowCount,
|
|
120
|
+
columnCount,
|
|
121
|
+
isEditable,
|
|
122
|
+
}: NextEditableCellArgs): TMDataGridCellCoords | null {
|
|
123
|
+
const total = rowCount * columnCount;
|
|
124
|
+
if (total === 0) return null;
|
|
125
|
+
const start = from.rowIndex * columnCount + from.columnIndex;
|
|
126
|
+
for (let step = 1; step < total; step += 1) {
|
|
127
|
+
const index = (start + direction * step + total * step) % total;
|
|
128
|
+
const coords = {
|
|
129
|
+
rowIndex: Math.floor(index / columnCount),
|
|
130
|
+
columnIndex: index % columnCount,
|
|
131
|
+
};
|
|
132
|
+
if (isEditable(coords)) return coords;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
96
137
|
/** Whether two positions address the same cell. `null` equals only `null`. */
|
|
97
138
|
export function isSameCell(
|
|
98
139
|
a: TMDataGridCellPosition | null,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
getDefaultOperator,
|
|
3
|
+
type TMDataGridColumnType,
|
|
4
|
+
type TMDataGridFilterOperator,
|
|
5
|
+
} from "./filterOperators";
|
|
2
6
|
import { DETAILS_COLUMN_ID } from "../components/TMDataGridDetailsColumn";
|
|
7
|
+
import { EDIT_COLUMN_ID } from "../components/TMDataGridEditColumn";
|
|
8
|
+
import { ROW_NUMBER_COLUMN_ID } from "../components/TMDataGridRowNumberColumn";
|
|
3
9
|
import { SELECT_COLUMN_ID } from "../components/TMDataGridSelectColumn";
|
|
4
10
|
import type { TMDataGridColumnMeta } from "../useTMDataGrid";
|
|
5
11
|
|
|
@@ -27,24 +33,43 @@ export function getColumnType(column: ColumnLike): TMDataGridColumnType {
|
|
|
27
33
|
return column.columnDef.meta?.type ?? "string";
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
/**
|
|
37
|
+
* The operator a fresh filter on this column starts with —
|
|
38
|
+
* `meta.defaultFilterOperator`, else the type's default.
|
|
39
|
+
*/
|
|
40
|
+
export function getColumnDefaultOperator(
|
|
41
|
+
column: ColumnLike,
|
|
42
|
+
): TMDataGridFilterOperator {
|
|
43
|
+
return (
|
|
44
|
+
column.columnDef.meta?.defaultFilterOperator ??
|
|
45
|
+
getDefaultOperator(getColumnType(column))
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
export function getColumnAlign(column: ColumnLike): "left" | "right" | "center" {
|
|
31
50
|
return column.columnDef.meta?.align ?? "left";
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
/**
|
|
35
|
-
* A generated lane holding one
|
|
36
|
-
*
|
|
54
|
+
* A generated lane holding one fixed-width thing — the checkbox, the details
|
|
55
|
+
* chevron, the row number.
|
|
37
56
|
*
|
|
38
57
|
* They are laid out differently from every other column: cell padding is sized
|
|
39
58
|
* for text and grows with the scale, which at `xl` squeezed a 16px checkbox out
|
|
40
|
-
* of its track, so these lanes centre their
|
|
41
|
-
* Their headers
|
|
59
|
+
* of its track, so these lanes centre their content in an unpadded cell.
|
|
60
|
+
* Their headers carry no column menu, and none of them is ever exported —
|
|
61
|
+
* they hold chrome, not values.
|
|
42
62
|
*
|
|
43
63
|
* The tree column is deliberately not one of them: it holds a label as well as a
|
|
44
64
|
* chevron, and wants the padding.
|
|
45
65
|
*/
|
|
46
66
|
export function isControlColumn(columnId: string): boolean {
|
|
47
|
-
return
|
|
67
|
+
return (
|
|
68
|
+
columnId === SELECT_COLUMN_ID ||
|
|
69
|
+
columnId === DETAILS_COLUMN_ID ||
|
|
70
|
+
columnId === EDIT_COLUMN_ID ||
|
|
71
|
+
columnId === ROW_NUMBER_COLUMN_ID
|
|
72
|
+
);
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
/**
|