@dashforge/tw 0.7.0-beta → 0.8.0-beta
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/CHANGELOG.md +193 -0
- package/dist/index.esm.js +1092 -74
- package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/src/components/DataGrid/dataGrid.types.d.ts +43 -0
- package/dist/src/components/DataGrid/dataGrid.types.d.ts.map +1 -1
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts +6 -0
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts.map +1 -1
- package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts +23 -0
- package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts.map +1 -0
- package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts +50 -0
- package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts.map +1 -0
- package/dist/src/components/DataGrid/resize/useColumnResize.d.ts +32 -0
- package/dist/src/components/DataGrid/resize/useColumnResize.d.ts.map +1 -0
- package/dist/src/components/DataGrid/visibility/ColumnVisibilityDialog.d.ts +33 -0
- package/dist/src/components/DataGrid/visibility/ColumnVisibilityDialog.d.ts.map +1 -0
- package/dist/src/components/Table/Table.d.ts.map +1 -1
- package/dist/src/components/Table/table.types.d.ts +69 -5
- package/dist/src/components/Table/table.types.d.ts.map +1 -1
- package/dist/src/components/_shared/data/useTableFilter.d.ts +30 -7
- package/dist/src/components/_shared/data/useTableFilter.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/DataGrid/DataGrid.test.tsx +422 -1
- package/src/components/DataGrid/DataGrid.tsx +421 -53
- package/src/components/DataGrid/dataGrid.types.ts +50 -1
- package/src/components/DataGrid/dataGrid.variants.ts +9 -1
- package/src/components/DataGrid/filters/ColumnFilters.tsx +432 -0
- package/src/components/DataGrid/reorder/useColumnReorder.test.ts +38 -0
- package/src/components/DataGrid/reorder/useColumnReorder.ts +144 -0
- package/src/components/DataGrid/resize/useColumnResize.ts +106 -0
- package/src/components/DataGrid/visibility/ColumnVisibilityDialog.tsx +200 -0
- package/src/components/Table/Table.tsx +14 -0
- package/src/components/Table/table.types.ts +74 -5
- package/src/components/_shared/data/useTableFilter.test.ts +166 -0
- package/src/components/_shared/data/useTableFilter.ts +97 -16
- package/src/index.ts +2 -0
|
@@ -4,14 +4,100 @@ import { stringifyForSearch } from './useTableSearch.js';
|
|
|
4
4
|
import type { TableFilterModel } from '../../Table/table.types.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
* Coerce a value to a number for range comparison. Returns
|
|
8
|
+
* `null` if not coercible.
|
|
9
|
+
*/
|
|
10
|
+
function toNumber(value: unknown): number | null {
|
|
11
|
+
if (value == null) return null;
|
|
12
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
13
|
+
if (typeof value === 'bigint') return Number(value);
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
const n = Number(value);
|
|
16
|
+
return Number.isFinite(n) ? n : null;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Coerce a value to a Date's epoch ms for date-range comparison.
|
|
23
|
+
* Accepts `Date` instances + ISO 8601 strings. Returns `null` if
|
|
24
|
+
* not coercible.
|
|
25
|
+
*/
|
|
26
|
+
function toDateMs(value: unknown): number | null {
|
|
27
|
+
if (value == null) return null;
|
|
28
|
+
if (value instanceof Date) return value.getTime();
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
const d = new Date(value);
|
|
31
|
+
const t = d.getTime();
|
|
32
|
+
return Number.isNaN(t) ? null : t;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Test one row × one filter. Returns `true` if the row passes the
|
|
39
|
+
* filter. The operator decides the predicate:
|
|
40
|
+
*
|
|
41
|
+
* - `contains` → substring match on stringified value (text filter)
|
|
42
|
+
* - `equals` → strict equality (boolean filter mostly)
|
|
43
|
+
* - `between` → `value: [min, max]` tuple. Each end can be `null`
|
|
44
|
+
* for an open range. Used by number range + date range filters.
|
|
12
45
|
*
|
|
13
|
-
*
|
|
14
|
-
|
|
46
|
+
* Empty filter is a no-op (passes every row).
|
|
47
|
+
*/
|
|
48
|
+
function passesFilter(rowValue: unknown, op: string, filterValue: unknown): boolean {
|
|
49
|
+
switch (op) {
|
|
50
|
+
case 'contains': {
|
|
51
|
+
const needle =
|
|
52
|
+
typeof filterValue === 'string' ? filterValue.trim().toLowerCase() : '';
|
|
53
|
+
if (!needle) return true;
|
|
54
|
+
return stringifyForSearch(rowValue).toLowerCase().includes(needle);
|
|
55
|
+
}
|
|
56
|
+
case 'equals': {
|
|
57
|
+
if (filterValue == null) return true;
|
|
58
|
+
return rowValue === filterValue;
|
|
59
|
+
}
|
|
60
|
+
case 'between': {
|
|
61
|
+
if (!Array.isArray(filterValue) || filterValue.length !== 2) return true;
|
|
62
|
+
const [min, max] = filterValue as [unknown, unknown];
|
|
63
|
+
if (min == null && max == null) return true;
|
|
64
|
+
|
|
65
|
+
// Try number range first.
|
|
66
|
+
const rowNum = toNumber(rowValue);
|
|
67
|
+
const minNum = min == null ? null : toNumber(min);
|
|
68
|
+
const maxNum = max == null ? null : toNumber(max);
|
|
69
|
+
if (rowNum != null && (minNum != null || maxNum != null)) {
|
|
70
|
+
if (minNum != null && rowNum < minNum) return false;
|
|
71
|
+
if (maxNum != null && rowNum > maxNum) return false;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fall through to date range.
|
|
76
|
+
const rowMs = toDateMs(rowValue);
|
|
77
|
+
const minMs = min == null ? null : toDateMs(min);
|
|
78
|
+
const maxMs = max == null ? null : toDateMs(max);
|
|
79
|
+
if (rowMs != null && (minMs != null || maxMs != null)) {
|
|
80
|
+
if (minMs != null && rowMs < minMs) return false;
|
|
81
|
+
if (maxMs != null && rowMs > maxMs) return false;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If neither coercion worked, let the row pass (defensive).
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Filters `rows` by the per-column filter model. AND semantics
|
|
95
|
+
* across columns — a row must pass EVERY active filter.
|
|
96
|
+
*
|
|
97
|
+
* Each filter item specifies an `op` (`'contains'` / `'equals'` /
|
|
98
|
+
* `'between'`) and a `value` whose shape matches the operator
|
|
99
|
+
* (string for contains; primitive for equals; `[min, max]` tuple
|
|
100
|
+
* for between).
|
|
15
101
|
*/
|
|
16
102
|
export function useTableFilter<T extends object>(
|
|
17
103
|
rows: T[],
|
|
@@ -22,16 +108,11 @@ export function useTableFilter<T extends object>(
|
|
|
22
108
|
return rows.filter((row) =>
|
|
23
109
|
filterModel.every((filter) => {
|
|
24
110
|
const value = getNestedValue(row, filter.field);
|
|
25
|
-
|
|
26
|
-
const needle = filter.value.trim().toLowerCase();
|
|
27
|
-
if (!needle) return true; // empty filter == no-op
|
|
28
|
-
switch (filter.op) {
|
|
29
|
-
case 'contains':
|
|
30
|
-
return stringValue.includes(needle);
|
|
31
|
-
default:
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
111
|
+
return passesFilter(value, filter.op, filter.value);
|
|
34
112
|
}),
|
|
35
113
|
);
|
|
36
114
|
}, [rows, filterModel]);
|
|
37
115
|
}
|
|
116
|
+
|
|
117
|
+
// Exposed for testing.
|
|
118
|
+
export { passesFilter, toNumber, toDateMs };
|