@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.
Files changed (62) hide show
  1. package/README.md +25 -2
  2. package/dist/index.d.ts +856 -14
  3. package/dist/index.js +3370 -1546
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/dist/styles.layer.css +1 -0
  7. package/package.json +12 -2
  8. package/skills/columns/SKILL.md +1 -1
  9. package/skills/features/SKILL.md +1 -1
  10. package/skills/getting-started/SKILL.md +1 -1
  11. package/skills/options/SKILL.md +1 -1
  12. package/skills/server-side/SKILL.md +1 -1
  13. package/skills/testing/SKILL.md +198 -0
  14. package/src/tmdatagrid/components/TMDataGrid.module.css +11 -0
  15. package/src/tmdatagrid/components/TMDataGrid.tsx +34 -0
  16. package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +256 -0
  17. package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +35 -12
  18. package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +3 -0
  19. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +62 -0
  20. package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +237 -0
  21. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +164 -0
  22. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +77 -62
  23. package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +6 -0
  24. package/src/tmdatagrid/components/TMDataGridFooter.tsx +10 -3
  25. package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +2 -0
  26. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +15 -2
  27. package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +21 -4
  28. package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +44 -0
  29. package/src/tmdatagrid/components/TMDataGridSearch.tsx +2 -0
  30. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +3 -0
  31. package/src/tmdatagrid/components/TMDataGridTable.module.css +206 -0
  32. package/src/tmdatagrid/components/TMDataGridTable.tsx +841 -57
  33. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +12 -7
  34. package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +29 -0
  35. package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +57 -0
  36. package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +45 -0
  37. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +38 -0
  38. package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +53 -0
  39. package/src/tmdatagrid/components/editors/TMDataGridStringEditor.tsx +31 -0
  40. package/src/tmdatagrid/components/editors/editorShared.ts +48 -0
  41. package/src/tmdatagrid/components/filters/DgAutocompleteFilter.tsx +46 -0
  42. package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +43 -0
  43. package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +52 -0
  44. package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +38 -0
  45. package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +122 -0
  46. package/src/tmdatagrid/components/icons.ts +4 -0
  47. package/src/tmdatagrid/core/autosize.ts +1 -1
  48. package/src/tmdatagrid/core/capabilities.ts +37 -1
  49. package/src/tmdatagrid/core/cellNavigation.ts +41 -0
  50. package/src/tmdatagrid/core/columnUtils.ts +31 -6
  51. package/src/tmdatagrid/core/editEngine.ts +857 -0
  52. package/src/tmdatagrid/core/filterControls.ts +45 -0
  53. package/src/tmdatagrid/core/filterOperators.ts +72 -4
  54. package/src/tmdatagrid/core/labels.ts +68 -1
  55. package/src/tmdatagrid/core/labelsSv.ts +24 -1
  56. package/src/tmdatagrid/core/matchHighlight.ts +96 -0
  57. package/src/tmdatagrid/core/persistence.ts +97 -3
  58. package/src/tmdatagrid/core/quickSearch.ts +144 -0
  59. package/src/tmdatagrid/core/rowPinning.ts +37 -0
  60. package/src/tmdatagrid/core/rowSelection.ts +10 -1
  61. package/src/tmdatagrid/index.ts +51 -0
  62. 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-testid="dg-header-${CSS.escape(columnId)}"] [data-dg-header-title]`,
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
- const cellSelectionMode = options.cellSelection ?? "none";
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 type { TMDataGridColumnType } from "./filterOperators";
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 control at a fixed width — the checkbox and the
36
- * details chevron.
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 control in an unpadded cell.
41
- * Their headers are controls too, and carry no column menu.
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 columnId === SELECT_COLUMN_ID || columnId === DETAILS_COLUMN_ID;
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
  /**