@dashforge/tw 0.7.0-beta → 0.8.1-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.
Files changed (37) hide show
  1. package/CHANGELOG.md +255 -0
  2. package/dist/index.esm.js +1057 -74
  3. package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -1
  4. package/dist/src/components/DataGrid/dataGrid.types.d.ts +43 -0
  5. package/dist/src/components/DataGrid/dataGrid.types.d.ts.map +1 -1
  6. package/dist/src/components/DataGrid/dataGrid.variants.d.ts +6 -0
  7. package/dist/src/components/DataGrid/dataGrid.variants.d.ts.map +1 -1
  8. package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts +23 -0
  9. package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts.map +1 -0
  10. package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts +50 -0
  11. package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts.map +1 -0
  12. package/dist/src/components/DataGrid/resize/useColumnResize.d.ts +32 -0
  13. package/dist/src/components/DataGrid/resize/useColumnResize.d.ts.map +1 -0
  14. package/dist/src/components/DataGrid/visibility/ColumnVisibilityMenu.d.ts +40 -0
  15. package/dist/src/components/DataGrid/visibility/ColumnVisibilityMenu.d.ts.map +1 -0
  16. package/dist/src/components/Table/Table.d.ts.map +1 -1
  17. package/dist/src/components/Table/table.types.d.ts +67 -5
  18. package/dist/src/components/Table/table.types.d.ts.map +1 -1
  19. package/dist/src/components/_shared/data/useTableFilter.d.ts +30 -7
  20. package/dist/src/components/_shared/data/useTableFilter.d.ts.map +1 -1
  21. package/dist/src/index.d.ts +1 -1
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/package.json +3 -3
  24. package/src/components/DataGrid/DataGrid.test.tsx +418 -1
  25. package/src/components/DataGrid/DataGrid.tsx +416 -53
  26. package/src/components/DataGrid/dataGrid.types.ts +50 -1
  27. package/src/components/DataGrid/dataGrid.variants.ts +9 -1
  28. package/src/components/DataGrid/filters/ColumnFilters.tsx +432 -0
  29. package/src/components/DataGrid/reorder/useColumnReorder.test.ts +38 -0
  30. package/src/components/DataGrid/reorder/useColumnReorder.ts +144 -0
  31. package/src/components/DataGrid/resize/useColumnResize.ts +106 -0
  32. package/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx +169 -0
  33. package/src/components/Table/Table.tsx +11 -0
  34. package/src/components/Table/table.types.ts +72 -5
  35. package/src/components/_shared/data/useTableFilter.test.ts +166 -0
  36. package/src/components/_shared/data/useTableFilter.ts +97 -16
  37. 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
- * Filters `rows` by the per-column filter model. v1 only supports
8
- * the `contains` operator (text substring) — additional ops
9
- * (`equals`, `in`, `gte`, `lte`, `between`, `before`, `after`)
10
- * land in v1-bis once the filter UI surface for each shape is
11
- * fleshed out.
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
- * AND semantics across columns: a row must match EVERY active
14
- * filter. Within a single filter, the op decides the predicate.
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
- const stringValue = stringifyForSearch(value).toLowerCase();
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 };
package/src/index.ts CHANGED
@@ -294,6 +294,8 @@ export type {
294
294
  TableSortModel,
295
295
  TableFilterItem,
296
296
  TableFilterModel,
297
+ TableFilterOperator,
298
+ TableFilterType,
297
299
  TableRowSelectionMode,
298
300
  TableExpandableConfig,
299
301
  TableLabels,