@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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compareItems,
|
|
3
|
+
rankItem,
|
|
4
|
+
type RankingInfo,
|
|
5
|
+
} from "@tanstack/match-sorter-utils";
|
|
6
|
+
import {
|
|
7
|
+
createSortedRowModel,
|
|
8
|
+
type Row,
|
|
9
|
+
type RowData,
|
|
10
|
+
type RowModel,
|
|
11
|
+
type Table,
|
|
12
|
+
type TableFeatures,
|
|
13
|
+
} from "@tanstack/react-table";
|
|
14
|
+
|
|
15
|
+
/** How the quick search matches — see `quickSearchMode` on the hook options. */
|
|
16
|
+
export type TMDataGridQuickSearchMode = "fuzzy" | "contains";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The fuzzy filter's registry name — what `globalFilterFn` resolves to while
|
|
20
|
+
* `quickSearchMode` is `"fuzzy"`. The rank ordering keys off this name, so an
|
|
21
|
+
* explicit `globalFilterFn` override switches the ordering off with it.
|
|
22
|
+
*/
|
|
23
|
+
export const FUZZY_FILTER_FN_NAME = "tmDataGridFuzzy";
|
|
24
|
+
|
|
25
|
+
/** What {@link fuzzyGlobalFilterFn} records per column via `addMeta`. */
|
|
26
|
+
type FuzzyFilterMeta = { fuzzyRank: RankingInfo };
|
|
27
|
+
|
|
28
|
+
function isFuzzyFilterMeta(value: unknown): value is FuzzyFilterMeta {
|
|
29
|
+
if (typeof value !== "object" || value === null) return false;
|
|
30
|
+
const rank = (value as { fuzzyRank?: unknown }).fuzzyRank;
|
|
31
|
+
return (
|
|
32
|
+
typeof rank === "object" &&
|
|
33
|
+
rank !== null &&
|
|
34
|
+
typeof (rank as { rank?: unknown }).rank === "number"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Match-sorter ranking as the global filter: a row passes when a globally
|
|
40
|
+
* filterable column ranks above match-sorter's threshold — typos and skipped
|
|
41
|
+
* characters forgiven — and the rank is recorded for the ordering to read.
|
|
42
|
+
*
|
|
43
|
+
* TanStack stops probing a row's columns at the first passing one, so the
|
|
44
|
+
* recorded ranks cover the columns up to and including it; the best of those
|
|
45
|
+
* stands for the row. Good enough for a quick search: the first passing
|
|
46
|
+
* column is almost always the one the user is looking at.
|
|
47
|
+
*/
|
|
48
|
+
export function fuzzyGlobalFilterFn<
|
|
49
|
+
TFeatures extends TableFeatures,
|
|
50
|
+
TData extends RowData,
|
|
51
|
+
>(
|
|
52
|
+
row: Row<TFeatures, TData>,
|
|
53
|
+
columnId: string,
|
|
54
|
+
filterValue: unknown,
|
|
55
|
+
addMeta?: (meta: FuzzyFilterMeta) => void,
|
|
56
|
+
): boolean {
|
|
57
|
+
const rank = rankItem(
|
|
58
|
+
String(row.getValue(columnId) ?? ""),
|
|
59
|
+
String(filterValue),
|
|
60
|
+
);
|
|
61
|
+
addMeta?.({ fuzzyRank: rank });
|
|
62
|
+
return rank.passed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The best fuzzy rank any column recorded for this row, or `null`. */
|
|
66
|
+
function bestRank<TFeatures extends TableFeatures, TData extends RowData>(
|
|
67
|
+
row: Row<TFeatures, TData>,
|
|
68
|
+
): RankingInfo | null {
|
|
69
|
+
let best: RankingInfo | null = null;
|
|
70
|
+
// As a plain record, for the same generic-features reason as the options
|
|
71
|
+
// read in isRankingActive.
|
|
72
|
+
const { columnFiltersMeta } = row as {
|
|
73
|
+
columnFiltersMeta?: Record<string, unknown>;
|
|
74
|
+
};
|
|
75
|
+
for (const meta of Object.values(columnFiltersMeta ?? {})) {
|
|
76
|
+
if (!isFuzzyFilterMeta(meta)) continue;
|
|
77
|
+
if (best === null || meta.fuzzyRank.rank > best.rank) best = meta.fuzzyRank;
|
|
78
|
+
}
|
|
79
|
+
return best;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether the ranked ordering applies: the fuzzy quick search is narrowing
|
|
84
|
+
* the grid, and nothing else claims an order — no sort, no grouping.
|
|
85
|
+
*/
|
|
86
|
+
function isRankingActive<TFeatures extends TableFeatures, TData extends RowData>(
|
|
87
|
+
table: Table<TFeatures, TData>,
|
|
88
|
+
): boolean {
|
|
89
|
+
// Off the options as a plain record: a generic `TFeatures` does not carry
|
|
90
|
+
// the global-filtering feature's option types, and this file stays generic
|
|
91
|
+
// to avoid a cycle with useTMDataGrid.
|
|
92
|
+
const { globalFilterFn } = table.options as { globalFilterFn?: unknown };
|
|
93
|
+
if (globalFilterFn !== FUZZY_FILTER_FN_NAME) return false;
|
|
94
|
+
const state = table.store.state as {
|
|
95
|
+
sorting?: Array<unknown>;
|
|
96
|
+
grouping?: Array<unknown>;
|
|
97
|
+
globalFilter?: unknown;
|
|
98
|
+
};
|
|
99
|
+
return (
|
|
100
|
+
(state.sorting?.length ?? 0) === 0 &&
|
|
101
|
+
(state.grouping?.length ?? 0) === 0 &&
|
|
102
|
+
typeof state.globalFilter === "string" &&
|
|
103
|
+
state.globalFilter.trim() !== ""
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The sorted row model, with one addition: while {@link isRankingActive},
|
|
109
|
+
* rows order by match quality, best first. Declarative — the ordering is
|
|
110
|
+
* derived from filter state, never written into `sorting`, so no column
|
|
111
|
+
* claims `aria-sort`, the persisted slice stays untouched, and the user's
|
|
112
|
+
* next sort click suspends it just by existing.
|
|
113
|
+
*
|
|
114
|
+
* Sits upstream of pagination, so a ranked first page really is the best
|
|
115
|
+
* matches of the whole set.
|
|
116
|
+
*/
|
|
117
|
+
export function createFuzzyRankedSortedRowModel<
|
|
118
|
+
TFeatures extends TableFeatures,
|
|
119
|
+
TData extends RowData,
|
|
120
|
+
>(): (table: Table<TFeatures, TData>) => () => RowModel<TFeatures, TData> {
|
|
121
|
+
const base = createSortedRowModel<TFeatures, TData>();
|
|
122
|
+
return (table) => {
|
|
123
|
+
const getSorted = base(table);
|
|
124
|
+
// One-slot memo: every input of the ordering — filter text, data, the
|
|
125
|
+
// sorting and grouping states — changes the sorted model's identity, so
|
|
126
|
+
// the model alone is the cache key.
|
|
127
|
+
let lastModel: RowModel<TFeatures, TData> | null = null;
|
|
128
|
+
let ranked: RowModel<TFeatures, TData> | null = null;
|
|
129
|
+
return () => {
|
|
130
|
+
const model = getSorted();
|
|
131
|
+
if (!isRankingActive(table)) return model;
|
|
132
|
+
if (model === lastModel && ranked !== null) return ranked;
|
|
133
|
+
const rows = [...model.rows].sort((a, b) => {
|
|
134
|
+
const rankA = bestRank(a);
|
|
135
|
+
const rankB = bestRank(b);
|
|
136
|
+
if (rankA !== null && rankB !== null) return compareItems(rankA, rankB);
|
|
137
|
+
return rankA !== null ? -1 : rankB !== null ? 1 : 0;
|
|
138
|
+
});
|
|
139
|
+
ranked = { rows, flatRows: model.flatRows, rowsById: model.rowsById };
|
|
140
|
+
lastModel = model;
|
|
141
|
+
return ranked;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Row, RowData } from "@tanstack/react-table";
|
|
2
|
+
import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The rows pinned to one edge, resolved safely and in pin order.
|
|
6
|
+
*
|
|
7
|
+
* Not TanStack's `getTopRows()`: that resolves each pinned id with
|
|
8
|
+
* `getRow(id, true)`, which throws once the row's data is gone — and pinned
|
|
9
|
+
* ids outlive data. A delete, a server-side page swap, a refetch that comes
|
|
10
|
+
* back without the row: any of these would take the whole grid down. Here a
|
|
11
|
+
* stale id is skipped instead; it stays in state, harmless, and the row
|
|
12
|
+
* returns to its edge if its data comes back.
|
|
13
|
+
*
|
|
14
|
+
* The semantics are TanStack's `keepPinnedRows: true`: a pinned row stays at
|
|
15
|
+
* its edge even when a filter or the pager would have dropped it from the
|
|
16
|
+
* body. Ids resolve against the pre-pagination model first — those rows carry
|
|
17
|
+
* the live sort and filter context — with the core model as the fallback for
|
|
18
|
+
* rows the filters removed. A leaf whose group is collapsed stays hidden,
|
|
19
|
+
* as upstream does.
|
|
20
|
+
*/
|
|
21
|
+
export function readPinnedRows<TData extends RowData>(
|
|
22
|
+
table: TMDataGridTable<TData>,
|
|
23
|
+
position: "top" | "bottom",
|
|
24
|
+
): Array<Row<TMDataGridFeatures, TData>> {
|
|
25
|
+
const ids = table.store.state.rowPinning[position];
|
|
26
|
+
if (ids.length === 0) return [];
|
|
27
|
+
const visible = table.getPrePaginatedRowModel().rowsById;
|
|
28
|
+
const all = table.getCoreRowModel().rowsById;
|
|
29
|
+
const rows: Array<Row<TMDataGridFeatures, TData>> = [];
|
|
30
|
+
for (const id of ids) {
|
|
31
|
+
const row = visible[id] ?? all[id];
|
|
32
|
+
if (row === undefined) continue;
|
|
33
|
+
if (!row.getIsAllParentsExpanded()) continue;
|
|
34
|
+
rows.push(row);
|
|
35
|
+
}
|
|
36
|
+
return rows;
|
|
37
|
+
}
|
|
@@ -14,14 +14,23 @@ import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
|
|
|
14
14
|
* is the right list for a range: it is what the user can see, and a range that
|
|
15
15
|
* swept collapsed rows would select things off screen. What a group row in the
|
|
16
16
|
* range contributes is decided by {@link getSelectableRowIds}.
|
|
17
|
+
*
|
|
18
|
+
* Rows pinned to an edge leave this list — they render in their own sticky
|
|
19
|
+
* blocks, outside the scrolling order a shift-click range or the virtualizer
|
|
20
|
+
* walks. See `readPinnedRows`.
|
|
17
21
|
*/
|
|
18
22
|
export function getDisplayedRows<TData extends RowData>(
|
|
19
23
|
table: TMDataGridTable<TData>,
|
|
20
24
|
features: TMDataGridFeatureFlags,
|
|
21
25
|
): Array<Row<TMDataGridFeatures, TData>> {
|
|
22
|
-
|
|
26
|
+
const rows = isPagingActive(table, features)
|
|
23
27
|
? table.getPaginatedRowModel().rows
|
|
24
28
|
: table.getPrePaginatedRowModel().rows;
|
|
29
|
+
if (!features.rowPinning) return rows;
|
|
30
|
+
const { top, bottom } = table.store.state.rowPinning;
|
|
31
|
+
if (top.length === 0 && bottom.length === 0) return rows;
|
|
32
|
+
const pinned = new Set([...top, ...bottom]);
|
|
33
|
+
return rows.filter((row) => !pinned.has(row.id));
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
/**
|
package/src/tmdatagrid/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
} from "./TMDataGridContext";
|
|
8
8
|
export { SELECT_COLUMN_ID } from "./components/TMDataGridSelectColumn";
|
|
9
9
|
export { DETAILS_COLUMN_ID } from "./components/TMDataGridDetailsColumn";
|
|
10
|
+
export { EDIT_COLUMN_ID } from "./components/TMDataGridEditColumn";
|
|
11
|
+
export { ROW_NUMBER_COLUMN_ID } from "./components/TMDataGridRowNumberColumn";
|
|
10
12
|
export {
|
|
11
13
|
formatGroupValue,
|
|
12
14
|
GROUP_COLUMN_ID,
|
|
@@ -23,6 +25,9 @@ export {
|
|
|
23
25
|
type TMDataGridTable,
|
|
24
26
|
type TMDataGridTableMeta,
|
|
25
27
|
type TMDataGridUiActions,
|
|
28
|
+
type TMDataGridEditingOptions,
|
|
29
|
+
type TMDataGridScrollAlign,
|
|
30
|
+
type TMDataGridScrollToRowArgs,
|
|
26
31
|
type TMDataGridUiState,
|
|
27
32
|
type TMDataGridUiStore,
|
|
28
33
|
useTMDataGrid,
|
|
@@ -40,6 +45,7 @@ export {
|
|
|
40
45
|
type TMDataGridColumnType,
|
|
41
46
|
type TMDataGridFilterOperator,
|
|
42
47
|
type TMDataGridFilterValue,
|
|
48
|
+
emptyValueForOperator,
|
|
43
49
|
FILTER_OPERATOR_LABELS,
|
|
44
50
|
formatFilterLabel,
|
|
45
51
|
getDefaultOperator,
|
|
@@ -47,7 +53,12 @@ export {
|
|
|
47
53
|
isFilterActive,
|
|
48
54
|
operatorNeedsValue,
|
|
49
55
|
operatorTakesArrayValue,
|
|
56
|
+
operatorTakesRangeValue,
|
|
50
57
|
} from "./core/filterOperators";
|
|
58
|
+
export {
|
|
59
|
+
fuzzyGlobalFilterFn,
|
|
60
|
+
type TMDataGridQuickSearchMode,
|
|
61
|
+
} from "./core/quickSearch";
|
|
51
62
|
export {
|
|
52
63
|
optionsToComboboxData,
|
|
53
64
|
resolveColumnOptions,
|
|
@@ -55,6 +66,42 @@ export {
|
|
|
55
66
|
type TMDataGridOptionsArgs,
|
|
56
67
|
type TMDataGridOptionsSource,
|
|
57
68
|
} from "./core/columnOptions";
|
|
69
|
+
export { TMDataGridEditActions } from "./components/TMDataGridEditActions";
|
|
70
|
+
export {
|
|
71
|
+
clearedValueForType,
|
|
72
|
+
getEditFieldName,
|
|
73
|
+
normalizeFieldValidate,
|
|
74
|
+
type TMDataGridEditApi,
|
|
75
|
+
type TMDataGridEditChange,
|
|
76
|
+
type TMDataGridEditCommitArgs,
|
|
77
|
+
type TMDataGridEditCommitBatchArgs,
|
|
78
|
+
type TMDataGridEditField,
|
|
79
|
+
type TMDataGridEditMode,
|
|
80
|
+
type TMDataGridEditorArgs,
|
|
81
|
+
type TMDataGridEditorComponent,
|
|
82
|
+
type TMDataGridEditRowProjection,
|
|
83
|
+
type TMDataGridEditState,
|
|
84
|
+
type TMDataGridFieldValidate,
|
|
85
|
+
type TMDataGridRowAddArgs,
|
|
86
|
+
type TMDataGridRowDeleteArgs,
|
|
87
|
+
type TMDataGridRowEditForm,
|
|
88
|
+
type TMDataGridRowValidators,
|
|
89
|
+
} from "./core/editEngine";
|
|
90
|
+
export type {
|
|
91
|
+
TMDataGridFilterControlArgs,
|
|
92
|
+
TMDataGridFilterControlComponent,
|
|
93
|
+
} from "./core/filterControls";
|
|
94
|
+
export { TMDataGridFilterValueInput } from "./components/filters/TMDataGridFilterValueInput";
|
|
95
|
+
export { DgRangeSliderFilter } from "./components/filters/DgRangeSliderFilter";
|
|
96
|
+
export { DgDateRangeFilter } from "./components/filters/DgDateRangeFilter";
|
|
97
|
+
export { DgAutocompleteFilter } from "./components/filters/DgAutocompleteFilter";
|
|
98
|
+
export { DgTriStateFilter } from "./components/filters/DgTriStateFilter";
|
|
99
|
+
export { TMDataGridStringEditor } from "./components/editors/TMDataGridStringEditor";
|
|
100
|
+
export { TMDataGridNumberEditor } from "./components/editors/TMDataGridNumberEditor";
|
|
101
|
+
export { TMDataGridBooleanEditor } from "./components/editors/TMDataGridBooleanEditor";
|
|
102
|
+
export { TMDataGridDateEditor } from "./components/editors/TMDataGridDateEditor";
|
|
103
|
+
export { TMDataGridSelectEditor } from "./components/editors/TMDataGridSelectEditor";
|
|
104
|
+
export { TMDataGridMultiSelectEditor } from "./components/editors/TMDataGridMultiSelectEditor";
|
|
58
105
|
export {
|
|
59
106
|
mergeLabels,
|
|
60
107
|
TMDATAGRID_LABELS_EN,
|
|
@@ -63,6 +110,7 @@ export {
|
|
|
63
110
|
} from "./core/labels";
|
|
64
111
|
export { TMDATAGRID_LABELS_SV } from "./core/labelsSv";
|
|
65
112
|
export {
|
|
113
|
+
getColumnDefaultOperator,
|
|
66
114
|
getColumnLabel,
|
|
67
115
|
getColumnType,
|
|
68
116
|
isColumnReorderable,
|
|
@@ -147,6 +195,7 @@ export {
|
|
|
147
195
|
} from "./core/sizes";
|
|
148
196
|
export {
|
|
149
197
|
DATA_STATE_SLICES,
|
|
198
|
+
PERSIST_PAYLOAD_VERSION,
|
|
150
199
|
SETTINGS_STATE_SLICES,
|
|
151
200
|
type TMDataGridDataSlice,
|
|
152
201
|
type TMDataGridPersistence,
|
|
@@ -155,10 +204,12 @@ export {
|
|
|
155
204
|
type TMDataGridStorageMode,
|
|
156
205
|
} from "./core/persistence";
|
|
157
206
|
export type {
|
|
207
|
+
TMDataGridCellEventArgs,
|
|
158
208
|
TMDataGridCellNav,
|
|
159
209
|
TMDataGridColumnLayout,
|
|
160
210
|
TMDataGridRowContextMenu,
|
|
161
211
|
TMDataGridRowContextMenuArgs,
|
|
212
|
+
TMDataGridRowStyle,
|
|
162
213
|
TMDataGridTableProps,
|
|
163
214
|
} from "./components/TMDataGridTable";
|
|
164
215
|
export {
|