@tanstack/table-core 9.0.0-beta.46 → 9.0.0-beta.47

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 (116) hide show
  1. package/dist/features/column-filtering/columnFilteringFeature.types.d.cts +43 -3
  2. package/dist/features/column-filtering/columnFilteringFeature.types.d.ts +43 -3
  3. package/dist/features/column-filtering/columnFilteringFeature.utils.cjs +18 -9
  4. package/dist/features/column-filtering/columnFilteringFeature.utils.cjs.map +1 -1
  5. package/dist/features/column-filtering/columnFilteringFeature.utils.d.cts +6 -2
  6. package/dist/features/column-filtering/columnFilteringFeature.utils.d.ts +6 -2
  7. package/dist/features/column-filtering/columnFilteringFeature.utils.js +18 -9
  8. package/dist/features/column-filtering/columnFilteringFeature.utils.js.map +1 -1
  9. package/dist/features/column-filtering/createFilteredRowModel.cjs +7 -2
  10. package/dist/features/column-filtering/createFilteredRowModel.cjs.map +1 -1
  11. package/dist/features/column-filtering/createFilteredRowModel.d.cts +6 -2
  12. package/dist/features/column-filtering/createFilteredRowModel.d.ts +6 -2
  13. package/dist/features/column-filtering/createFilteredRowModel.js +7 -2
  14. package/dist/features/column-filtering/createFilteredRowModel.js.map +1 -1
  15. package/dist/features/column-grouping/columnGroupingFeature.types.d.cts +41 -3
  16. package/dist/features/column-grouping/columnGroupingFeature.types.d.ts +41 -3
  17. package/dist/features/column-grouping/columnGroupingFeature.utils.cjs +17 -3
  18. package/dist/features/column-grouping/columnGroupingFeature.utils.cjs.map +1 -1
  19. package/dist/features/column-grouping/columnGroupingFeature.utils.d.cts +5 -0
  20. package/dist/features/column-grouping/columnGroupingFeature.utils.d.ts +5 -0
  21. package/dist/features/column-grouping/columnGroupingFeature.utils.js +17 -3
  22. package/dist/features/column-grouping/columnGroupingFeature.utils.js.map +1 -1
  23. package/dist/features/column-grouping/createGroupedRowModel.cjs +6 -3
  24. package/dist/features/column-grouping/createGroupedRowModel.cjs.map +1 -1
  25. package/dist/features/column-grouping/createGroupedRowModel.d.cts +6 -3
  26. package/dist/features/column-grouping/createGroupedRowModel.d.ts +6 -3
  27. package/dist/features/column-grouping/createGroupedRowModel.js +6 -3
  28. package/dist/features/column-grouping/createGroupedRowModel.js.map +1 -1
  29. package/dist/features/global-filtering/globalFilteringFeature.utils.cjs +3 -1
  30. package/dist/features/global-filtering/globalFilteringFeature.utils.cjs.map +1 -1
  31. package/dist/features/global-filtering/globalFilteringFeature.utils.d.cts +2 -6
  32. package/dist/features/global-filtering/globalFilteringFeature.utils.d.ts +2 -6
  33. package/dist/features/global-filtering/globalFilteringFeature.utils.js +3 -1
  34. package/dist/features/global-filtering/globalFilteringFeature.utils.js.map +1 -1
  35. package/dist/features/row-sorting/createSortedRowModel.cjs +6 -2
  36. package/dist/features/row-sorting/createSortedRowModel.cjs.map +1 -1
  37. package/dist/features/row-sorting/createSortedRowModel.d.cts +6 -2
  38. package/dist/features/row-sorting/createSortedRowModel.d.ts +6 -2
  39. package/dist/features/row-sorting/createSortedRowModel.js +6 -2
  40. package/dist/features/row-sorting/createSortedRowModel.js.map +1 -1
  41. package/dist/features/row-sorting/rowSortingFeature.types.d.cts +31 -2
  42. package/dist/features/row-sorting/rowSortingFeature.types.d.ts +31 -2
  43. package/dist/features/row-sorting/rowSortingFeature.utils.cjs +19 -4
  44. package/dist/features/row-sorting/rowSortingFeature.utils.cjs.map +1 -1
  45. package/dist/features/row-sorting/rowSortingFeature.utils.js +19 -4
  46. package/dist/features/row-sorting/rowSortingFeature.utils.js.map +1 -1
  47. package/dist/fns/aggregationFns.cjs +128 -57
  48. package/dist/fns/aggregationFns.cjs.map +1 -1
  49. package/dist/fns/aggregationFns.d.cts +72 -18
  50. package/dist/fns/aggregationFns.d.ts +72 -18
  51. package/dist/fns/aggregationFns.js +126 -58
  52. package/dist/fns/aggregationFns.js.map +1 -1
  53. package/dist/fns/filterFns.cjs +253 -96
  54. package/dist/fns/filterFns.cjs.map +1 -1
  55. package/dist/fns/filterFns.d.cts +126 -94
  56. package/dist/fns/filterFns.d.ts +126 -94
  57. package/dist/fns/filterFns.js +246 -97
  58. package/dist/fns/filterFns.js.map +1 -1
  59. package/dist/fns/sortFns.cjs +68 -21
  60. package/dist/fns/sortFns.cjs.map +1 -1
  61. package/dist/fns/sortFns.d.cts +48 -15
  62. package/dist/fns/sortFns.d.ts +48 -15
  63. package/dist/fns/sortFns.js +68 -22
  64. package/dist/fns/sortFns.js.map +1 -1
  65. package/dist/helpers/tableFeatures.cjs +5 -4
  66. package/dist/helpers/tableFeatures.cjs.map +1 -1
  67. package/dist/helpers/tableFeatures.d.cts +5 -4
  68. package/dist/helpers/tableFeatures.d.ts +5 -4
  69. package/dist/helpers/tableFeatures.js +5 -4
  70. package/dist/helpers/tableFeatures.js.map +1 -1
  71. package/dist/index.cjs +12 -0
  72. package/dist/index.d.cts +8 -8
  73. package/dist/index.d.ts +8 -8
  74. package/dist/index.js +4 -4
  75. package/dist/types/TableFeatures.d.cts +15 -7
  76. package/dist/types/TableFeatures.d.ts +15 -7
  77. package/dist/types/type-utils.d.cts +8 -1
  78. package/dist/types/type-utils.d.ts +8 -1
  79. package/package.json +1 -1
  80. package/skills/api-not-found/SKILL.md +1 -1
  81. package/skills/client-vs-server/SKILL.md +1 -1
  82. package/skills/column-faceting/SKILL.md +11 -5
  83. package/skills/column-filtering/SKILL.md +8 -3
  84. package/skills/column-ordering/SKILL.md +1 -1
  85. package/skills/column-pinning/SKILL.md +1 -1
  86. package/skills/column-resizing/SKILL.md +1 -1
  87. package/skills/column-sizing/SKILL.md +1 -1
  88. package/skills/column-visibility/SKILL.md +1 -1
  89. package/skills/core/SKILL.md +1 -1
  90. package/skills/custom-features/SKILL.md +1 -1
  91. package/skills/expanding/SKILL.md +1 -1
  92. package/skills/global-filtering/SKILL.md +3 -3
  93. package/skills/grouping/SKILL.md +8 -3
  94. package/skills/migrate-v8-to-v9/SKILL.md +7 -7
  95. package/skills/pagination/SKILL.md +1 -1
  96. package/skills/row-pinning/SKILL.md +1 -1
  97. package/skills/row-selection/SKILL.md +1 -1
  98. package/skills/sorting/SKILL.md +9 -3
  99. package/skills/table-features/SKILL.md +16 -13
  100. package/skills/typescript/SKILL.md +1 -1
  101. package/src/features/column-filtering/columnFilteringFeature.types.ts +60 -0
  102. package/src/features/column-filtering/columnFilteringFeature.utils.ts +33 -19
  103. package/src/features/column-filtering/createFilteredRowModel.ts +13 -3
  104. package/src/features/column-grouping/columnGroupingFeature.types.ts +63 -8
  105. package/src/features/column-grouping/columnGroupingFeature.utils.ts +43 -9
  106. package/src/features/column-grouping/createGroupedRowModel.ts +6 -3
  107. package/src/features/global-filtering/globalFilteringFeature.utils.ts +8 -0
  108. package/src/features/row-sorting/createSortedRowModel.ts +6 -2
  109. package/src/features/row-sorting/rowSortingFeature.types.ts +53 -1
  110. package/src/features/row-sorting/rowSortingFeature.utils.ts +45 -13
  111. package/src/fns/aggregationFns.ts +204 -146
  112. package/src/fns/filterFns.ts +361 -283
  113. package/src/fns/sortFns.ts +95 -78
  114. package/src/helpers/tableFeatures.ts +5 -4
  115. package/src/types/TableFeatures.ts +15 -7
  116. package/src/types/type-utils.ts +8 -0
@@ -1,7 +1,72 @@
1
+ import type {
2
+ CreatedFilterFn,
3
+ ExtractFilterMeta,
4
+ FilterFnDef,
5
+ } from '../features/column-filtering/columnFilteringFeature.types'
1
6
  import type { RowData } from '../types/type-utils'
2
7
  import type { TableFeatures } from '../types/TableFeatures'
3
8
  import type { Row } from '../types/Row'
4
9
 
10
+ /**
11
+ * Builds a `FilterFn` from a value-level comparator plus optional resolvers.
12
+ *
13
+ * The `filter` comparator receives the row's data value (already passed
14
+ * through `resolveDataValue` when one is defined) and the filter value
15
+ * (already passed through `resolveFilterValue` by the table). Keeping
16
+ * normalization in the resolvers means a variant of an existing filter
17
+ * function only has to swap the resolvers, not re-implement the comparison.
18
+ *
19
+ * The definition is attached to the returned function, so a variant can be
20
+ * created by spreading a built-in filter function and overriding what differs:
21
+ *
22
+ * ```ts
23
+ * const normalize = (value: unknown) =>
24
+ * String(value ?? '')
25
+ * .toLowerCase()
26
+ * .normalize('NFD')
27
+ * .replace(/\p{Diacritic}/gu, '')
28
+ *
29
+ * const includesStringIgnoreDiacritics = constructFilterFn({
30
+ * ...filterFn_includesString,
31
+ * resolveFilterValue: normalize,
32
+ * resolveDataValue: normalize,
33
+ * })
34
+ * ```
35
+ *
36
+ * Note: the table applies `resolveFilterValue` once per filter before any rows
37
+ * are tested. When calling a filter function directly (outside of a table),
38
+ * apply it yourself: `fn(row, columnId, fn.resolveFilterValue?.(value) ?? value)`.
39
+ */
40
+ export function constructFilterFn<
41
+ TFeatures extends TableFeatures = any,
42
+ TData extends RowData = any,
43
+ >(def: FilterFnDef<TFeatures, TData>): CreatedFilterFn<TFeatures, TData> {
44
+ const filterFn: CreatedFilterFn<TFeatures, TData> = Object.assign(
45
+ <TRowFeatures extends TableFeatures, TRowData extends RowData>(
46
+ row: Row<TRowFeatures, TRowData>,
47
+ columnId: string,
48
+ filterValue: any,
49
+ addMeta?: (meta: ExtractFilterMeta<TRowFeatures>) => void,
50
+ ): boolean => {
51
+ const rawValue = row.getValue(columnId)
52
+ const dataValue = filterFn.resolveDataValue
53
+ ? filterFn.resolveDataValue(rawValue)
54
+ : rawValue
55
+ // The def's comparator is typed against the factory's TFeatures/TData;
56
+ // the caller's row generics are erased at this boundary.
57
+ return filterFn.filter(
58
+ dataValue,
59
+ filterValue,
60
+ row as any,
61
+ columnId,
62
+ addMeta as any,
63
+ )
64
+ },
65
+ def,
66
+ )
67
+ return filterFn
68
+ }
69
+
5
70
  // Basic filters
6
71
 
7
72
  /**
@@ -9,16 +74,10 @@ import type { Row } from '../types/Row'
9
74
  *
10
75
  * Uses JavaScript `===` comparison and auto-removes empty filter values.
11
76
  */
12
- export const filterFn_equals = Object.assign(
13
- <TFeatures extends TableFeatures, TData extends RowData>(
14
- row: Row<TFeatures, TData>,
15
- columnId: string,
16
- filterValue: unknown,
17
- ) => {
18
- return row.getValue(columnId) === filterValue
19
- },
20
- { autoRemove: (val: any) => testFalsy(val) },
21
- )
77
+ export const filterFn_equals = constructFilterFn({
78
+ filter: (dataValue, filterValue) => dataValue === filterValue,
79
+ autoRemove: (val: any) => testFalsy(val),
80
+ })
22
81
 
23
82
  /**
24
83
  * Keeps rows whose column value is loosely equal to the filter value.
@@ -26,16 +85,10 @@ export const filterFn_equals = Object.assign(
26
85
  * Uses JavaScript `==` comparison and auto-removes empty filter values. This is
27
86
  * useful for matching string input against numeric row values.
28
87
  */
29
- export const filterFn_weakEquals = Object.assign(
30
- <TFeatures extends TableFeatures, TData extends RowData>(
31
- row: Row<TFeatures, TData>,
32
- columnId: string,
33
- filterValue: unknown,
34
- ) => {
35
- return row.getValue(columnId) == filterValue
36
- },
37
- { autoRemove: (val: any) => testFalsy(val) },
38
- )
88
+ export const filterFn_weakEquals = constructFilterFn({
89
+ filter: (dataValue, filterValue) => dataValue == filterValue,
90
+ autoRemove: (val: any) => testFalsy(val),
91
+ })
39
92
 
40
93
  // String filters
41
94
 
@@ -44,21 +97,12 @@ export const filterFn_weakEquals = Object.assign(
44
97
  *
45
98
  * Matching is case-sensitive and empty filter values are auto-removed.
46
99
  */
47
- export const filterFn_includesStringSensitive = Object.assign(
48
- <TFeatures extends TableFeatures, TData extends RowData>(
49
- row: Row<TFeatures, TData>,
50
- columnId: string,
51
- filterValue: unknown,
52
- ) => {
53
- return Boolean(
54
- row.getValue(columnId)?.toString().includes(String(filterValue)),
55
- )
56
- },
57
- {
58
- autoRemove: (val: any) => testFalsy(val),
59
- resolveFilterValue: (val: any) => String(val),
60
- },
61
- )
100
+ export const filterFn_includesStringSensitive = constructFilterFn({
101
+ filter: (dataValue, filterValue) => Boolean(dataValue?.includes(filterValue)),
102
+ autoRemove: (val: any) => testFalsy(val),
103
+ resolveFilterValue: (val: any) => String(val),
104
+ resolveDataValue: (val: any) => (val == null ? undefined : String(val)),
105
+ })
62
106
 
63
107
  /**
64
108
  * Keeps rows whose stringified column value includes the filter text.
@@ -66,25 +110,13 @@ export const filterFn_includesStringSensitive = Object.assign(
66
110
  * Both values are lowercased before comparison, and empty filter values are
67
111
  * auto-removed.
68
112
  */
69
- export const filterFn_includesString = Object.assign(
70
- <TFeatures extends TableFeatures, TData extends RowData>(
71
- row: Row<TFeatures, TData>,
72
- columnId: string,
73
- filterValue: unknown,
74
- ) => {
75
- return Boolean(
76
- row
77
- .getValue(columnId)
78
- ?.toString()
79
- .toLowerCase()
80
- .includes(String(filterValue).toLowerCase()),
81
- )
82
- },
83
- {
84
- autoRemove: (val: any) => testFalsy(val),
85
- resolveFilterValue: (val: any) => String(val).toLowerCase(),
86
- },
87
- )
113
+ export const filterFn_includesString = constructFilterFn({
114
+ filter: (dataValue, filterValue) => Boolean(dataValue?.includes(filterValue)),
115
+ autoRemove: (val: any) => testFalsy(val),
116
+ resolveFilterValue: (val: any) => String(val).toLowerCase(),
117
+ resolveDataValue: (val: any) =>
118
+ val == null ? undefined : String(val).toLowerCase(),
119
+ })
88
120
 
89
121
  /**
90
122
  * Keeps rows whose stringified column value equals the filter text.
@@ -92,41 +124,80 @@ export const filterFn_includesString = Object.assign(
92
124
  * Both values are lowercased before comparison, and empty filter values are
93
125
  * auto-removed.
94
126
  */
95
- export const filterFn_equalsString = Object.assign(
96
- <TFeatures extends TableFeatures, TData extends RowData>(
97
- row: Row<TFeatures, TData>,
98
- columnId: string,
99
- filterValue: unknown,
100
- ) => {
101
- return (
102
- row.getValue(columnId)?.toString().toLowerCase() ===
103
- String(filterValue).toLowerCase()
104
- )
105
- },
106
- {
107
- autoRemove: (val: any) => testFalsy(val),
108
- resolveFilterValue: (val: any) => String(val).toLowerCase(),
109
- },
110
- )
127
+ export const filterFn_equalsString = constructFilterFn({
128
+ filter: (dataValue, filterValue) => dataValue === filterValue,
129
+ autoRemove: (val: any) => testFalsy(val),
130
+ resolveFilterValue: (val: any) => String(val).toLowerCase(),
131
+ resolveDataValue: (val: any) =>
132
+ val == null ? undefined : String(val).toLowerCase(),
133
+ })
111
134
 
112
135
  /**
113
136
  * Keeps rows whose stringified column value exactly equals the filter text.
114
137
  *
115
138
  * Matching is case-sensitive and empty filter values are auto-removed.
116
139
  */
117
- export const filterFn_equalsStringSensitive = Object.assign(
118
- <TFeatures extends TableFeatures, TData extends RowData>(
119
- row: Row<TFeatures, TData>,
120
- columnId: string,
121
- filterValue: unknown,
122
- ) => {
123
- return row.getValue(columnId)?.toString() === String(filterValue)
124
- },
125
- {
126
- autoRemove: (val: any) => testFalsy(val),
127
- resolveFilterValue: (val: any) => String(val),
128
- },
129
- )
140
+ export const filterFn_equalsStringSensitive = constructFilterFn({
141
+ filter: (dataValue, filterValue) => dataValue === filterValue,
142
+ autoRemove: (val: any) => testFalsy(val),
143
+ resolveFilterValue: (val: any) => String(val),
144
+ resolveDataValue: (val: any) => (val == null ? undefined : String(val)),
145
+ })
146
+
147
+ /**
148
+ * Keeps rows whose stringified column value starts with the filter text.
149
+ *
150
+ * Both values are lowercased before comparison, and empty filter values are
151
+ * auto-removed.
152
+ */
153
+ export const filterFn_startsWith = constructFilterFn({
154
+ filter: (dataValue, filterValue) =>
155
+ Boolean(dataValue?.startsWith(filterValue)),
156
+ autoRemove: (val: any) => testFalsy(val),
157
+ resolveFilterValue: (val: any) => String(val).toLowerCase(),
158
+ resolveDataValue: (val: any) =>
159
+ val == null ? undefined : String(val).toLowerCase(),
160
+ })
161
+
162
+ /**
163
+ * Keeps rows whose stringified column value ends with the filter text.
164
+ *
165
+ * Both values are lowercased before comparison, and empty filter values are
166
+ * auto-removed.
167
+ */
168
+ export const filterFn_endsWith = constructFilterFn({
169
+ filter: (dataValue, filterValue) => Boolean(dataValue?.endsWith(filterValue)),
170
+ autoRemove: (val: any) => testFalsy(val),
171
+ resolveFilterValue: (val: any) => String(val).toLowerCase(),
172
+ resolveDataValue: (val: any) =>
173
+ val == null ? undefined : String(val).toLowerCase(),
174
+ })
175
+
176
+ // Blank filters
177
+
178
+ /**
179
+ * Keeps rows whose column value is empty.
180
+ *
181
+ * A value is empty when it is nullish or stringifies to whitespace only. The
182
+ * filter value acts as an on/off flag: `false` and blank values are
183
+ * auto-removed.
184
+ */
185
+ export const filterFn_empty = constructFilterFn({
186
+ filter: (dataValue) => testValueEmpty(dataValue),
187
+ autoRemove: (val: any) => testFalsy(val) || val === false,
188
+ })
189
+
190
+ /**
191
+ * Keeps rows whose column value is not empty.
192
+ *
193
+ * A value is empty when it is nullish or stringifies to whitespace only. The
194
+ * filter value acts as an on/off flag: `false` and blank values are
195
+ * auto-removed.
196
+ */
197
+ export const filterFn_notEmpty = constructFilterFn({
198
+ filter: (dataValue) => !testValueEmpty(dataValue),
199
+ autoRemove: (val: any) => testFalsy(val) || val === false,
200
+ })
130
201
 
131
202
  // Number filters
132
203
 
@@ -136,78 +207,44 @@ export const filterFn_equalsStringSensitive = Object.assign(
136
207
  * Numeric values are compared numerically when both sides can be coerced to
137
208
  * numbers; otherwise normalized strings are compared.
138
209
  */
139
- export const filterFn_greaterThan = Object.assign(
140
- <TFeatures extends TableFeatures, TData extends RowData>(
141
- row: Row<TFeatures, TData>,
142
- columnId: string,
143
- filterValue: unknown,
144
- ) => {
145
- const rowValue = row.getValue(columnId)
146
- const numericRowValue =
147
- rowValue === null || rowValue === undefined ? 0 : +rowValue
148
- const numericFilterValue = Number(filterValue)
149
-
150
- if (!isNaN(numericFilterValue) && !isNaN(numericRowValue)) {
151
- return numericRowValue > numericFilterValue
152
- }
153
-
154
- const stringValue = (rowValue ?? '').toString().toLowerCase().trim()
155
- const stringFilterValue = String(filterValue).toLowerCase().trim()
156
- return stringValue > stringFilterValue
157
- },
158
- { autoRemove: (val: any) => testFalsy(val) },
159
- )
210
+ export const filterFn_greaterThan = constructFilterFn({
211
+ filter: (dataValue, filterValue) =>
212
+ compareGreaterThan(dataValue, filterValue),
213
+ autoRemove: (val: any) => testFalsy(val),
214
+ })
160
215
 
161
216
  /**
162
217
  * Keeps rows whose value is greater than or equal to the filter value.
163
218
  *
164
- * Delegates to the built-in greater-than and equality comparisons.
219
+ * Delegates to the built-in greater-than and strict-equality comparisons.
165
220
  */
166
- export const filterFn_greaterThanOrEqualTo = Object.assign(
167
- <TFeatures extends TableFeatures, TData extends RowData>(
168
- row: Row<TFeatures, TData>,
169
- columnId: string,
170
- filterValue: unknown,
171
- ) => {
172
- return (
173
- filterFn_greaterThan(row, columnId, filterValue) ||
174
- filterFn_equals(row, columnId, filterValue)
175
- )
176
- },
177
- { autoRemove: (val: any) => testFalsy(val) },
178
- )
221
+ export const filterFn_greaterThanOrEqualTo = constructFilterFn({
222
+ filter: (dataValue, filterValue) =>
223
+ compareGreaterThanOrEqualTo(dataValue, filterValue),
224
+ autoRemove: (val: any) => testFalsy(val),
225
+ })
179
226
 
180
227
  /**
181
228
  * Keeps rows whose value is less than the filter value.
182
229
  *
183
230
  * This is implemented as the inverse of greater-than-or-equal comparison.
184
231
  */
185
- export const filterFn_lessThan = Object.assign(
186
- <TFeatures extends TableFeatures, TData extends RowData>(
187
- row: Row<TFeatures, TData>,
188
- columnId: string,
189
- filterValue: unknown,
190
- ) => {
191
- return !filterFn_greaterThanOrEqualTo(row, columnId, filterValue)
192
- },
193
- { autoRemove: (val: any) => testFalsy(val) },
194
- )
232
+ export const filterFn_lessThan = constructFilterFn({
233
+ filter: (dataValue, filterValue) =>
234
+ !compareGreaterThanOrEqualTo(dataValue, filterValue),
235
+ autoRemove: (val: any) => testFalsy(val),
236
+ })
195
237
 
196
238
  /**
197
239
  * Keeps rows whose value is less than or equal to the filter value.
198
240
  *
199
241
  * This is implemented as the inverse of greater-than comparison.
200
242
  */
201
- export const filterFn_lessThanOrEqualTo = Object.assign(
202
- <TFeatures extends TableFeatures, TData extends RowData>(
203
- row: Row<TFeatures, TData>,
204
- columnId: string,
205
- filterValue: unknown,
206
- ) => {
207
- return !filterFn_greaterThan(row, columnId, filterValue)
208
- },
209
- { autoRemove: (val: any) => testFalsy(val) },
210
- )
243
+ export const filterFn_lessThanOrEqualTo = constructFilterFn({
244
+ filter: (dataValue, filterValue) =>
245
+ !compareGreaterThan(dataValue, filterValue),
246
+ autoRemove: (val: any) => testFalsy(val),
247
+ })
211
248
 
212
249
  // Range filters
213
250
 
@@ -216,80 +253,26 @@ export const filterFn_lessThanOrEqualTo = Object.assign(
216
253
  *
217
254
  * Blank range endpoints are treated as open-ended.
218
255
  */
219
- const filterFn_between = Object.assign(
220
- <TFeatures extends TableFeatures, TData extends RowData>(
221
- row: Row<TFeatures, TData>,
222
- columnId: string,
223
- filterValues: [unknown, unknown],
224
- ): boolean => {
225
- const min = filterValues[0]
226
- if (min !== '' && min !== undefined) {
227
- if (!filterFn_greaterThan(row, columnId, min)) {
228
- return false
229
- }
230
- }
231
-
232
- const max = filterValues[1]
233
- if (max === '' || max === undefined) {
234
- return true
235
- }
236
-
237
- if (min !== '' && min !== undefined) {
238
- const numericMin = Number(min)
239
- const numericMax = Number(max)
240
- if (!isNaN(numericMin) && !isNaN(numericMax) && numericMin > numericMax) {
241
- return true
242
- }
243
- }
244
-
245
- return filterFn_lessThan(row, columnId, max)
246
- },
247
- {
248
- autoRemove: (val: any) =>
249
- testFalsy(val) ||
250
- (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
251
- },
252
- )
256
+ export const filterFn_between = constructFilterFn({
257
+ filter: (dataValue, filterValues: [unknown, unknown]) =>
258
+ compareBetween(dataValue, filterValues, false),
259
+ autoRemove: (val: any) =>
260
+ testFalsy(val) ||
261
+ (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
262
+ })
253
263
 
254
264
  /**
255
265
  * Keeps rows whose value falls between an inclusive min/max pair.
256
266
  *
257
267
  * Blank range endpoints are treated as open-ended.
258
268
  */
259
- const filterFn_betweenInclusive = Object.assign(
260
- <TFeatures extends TableFeatures, TData extends RowData>(
261
- row: Row<TFeatures, TData>,
262
- columnId: string,
263
- filterValues: [unknown, unknown],
264
- ): boolean => {
265
- const min = filterValues[0]
266
- if (min !== '' && min !== undefined) {
267
- if (!filterFn_greaterThanOrEqualTo(row, columnId, min)) {
268
- return false
269
- }
270
- }
271
-
272
- const max = filterValues[1]
273
- if (max === '' || max === undefined) {
274
- return true
275
- }
276
-
277
- if (min !== '' && min !== undefined) {
278
- const numericMin = Number(min)
279
- const numericMax = Number(max)
280
- if (!isNaN(numericMin) && !isNaN(numericMax) && numericMin > numericMax) {
281
- return true
282
- }
283
- }
284
-
285
- return filterFn_lessThanOrEqualTo(row, columnId, max)
286
- },
287
- {
288
- autoRemove: (val: any) =>
289
- testFalsy(val) ||
290
- (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
291
- },
292
- )
269
+ export const filterFn_betweenInclusive = constructFilterFn({
270
+ filter: (dataValue, filterValues: [unknown, unknown]) =>
271
+ compareBetween(dataValue, filterValues, true),
272
+ autoRemove: (val: any) =>
273
+ testFalsy(val) ||
274
+ (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
275
+ })
293
276
 
294
277
  /**
295
278
  * Keeps rows whose numeric value is inside an inclusive `[min, max]` range.
@@ -297,139 +280,156 @@ const filterFn_betweenInclusive = Object.assign(
297
280
  * Filter values are normalized so blank endpoints become open-ended and
298
281
  * reversed endpoints are swapped.
299
282
  */
300
- export const filterFn_inNumberRange = Object.assign(
301
- <TFeatures extends TableFeatures, TData extends RowData>(
302
- row: Row<TFeatures, TData>,
303
- columnId: string,
304
- filterValue: [number, number],
305
- ) => {
283
+ export const filterFn_inNumberRange = constructFilterFn({
284
+ filter: (dataValue: number, filterValue: [number, number]) => {
306
285
  const [min, max] = filterValue
286
+ return dataValue >= min && dataValue <= max
287
+ },
288
+ resolveFilterValue: (val: [any, any]) => {
289
+ const [unsafeMin, unsafeMax] = val
290
+
291
+ const parsedMin =
292
+ typeof unsafeMin !== 'number' ? parseFloat(unsafeMin) : unsafeMin
293
+ const parsedMax =
294
+ typeof unsafeMax !== 'number' ? parseFloat(unsafeMax) : unsafeMax
295
+
296
+ let min =
297
+ unsafeMin === null || Number.isNaN(parsedMin) ? -Infinity : parsedMin
298
+ let max =
299
+ unsafeMax === null || Number.isNaN(parsedMax) ? Infinity : parsedMax
300
+
301
+ if (min > max) {
302
+ const temp = min
303
+ min = max
304
+ max = temp
305
+ }
307
306
 
308
- const rowValue: number = row.getValue(columnId)
309
- return rowValue >= min && rowValue <= max
307
+ return [min, max] as const
310
308
  },
311
- {
312
- resolveFilterValue: (val: [any, any]) => {
313
- const [unsafeMin, unsafeMax] = val
314
-
315
- const parsedMin =
316
- typeof unsafeMin !== 'number' ? parseFloat(unsafeMin) : unsafeMin
317
- const parsedMax =
318
- typeof unsafeMax !== 'number' ? parseFloat(unsafeMax) : unsafeMax
319
-
320
- let min =
321
- unsafeMin === null || Number.isNaN(parsedMin) ? -Infinity : parsedMin
322
- let max =
323
- unsafeMax === null || Number.isNaN(parsedMax) ? Infinity : parsedMax
324
-
325
- if (min > max) {
326
- const temp = min
327
- min = max
328
- max = temp
329
- }
309
+ autoRemove: (val: any) =>
310
+ testFalsy(val) ||
311
+ (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
312
+ })
330
313
 
331
- return [min, max] as const
332
- },
333
- autoRemove: (val: any) =>
334
- testFalsy(val) ||
335
- (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
314
+ // Date filters
315
+
316
+ /**
317
+ * Keeps rows whose date value is inside an inclusive `[min, max]` date range.
318
+ *
319
+ * Row values and range endpoints may be `Date` objects, timestamps, or
320
+ * parseable date strings. Blank or invalid endpoints become open-ended and
321
+ * reversed endpoints are swapped. Rows without a valid date never match.
322
+ */
323
+ export const filterFn_inDateRange = constructFilterFn({
324
+ filter: (dataValue: number, filterValue: [number, number]) => {
325
+ const [min, max] = filterValue
326
+ return dataValue >= min && dataValue <= max
336
327
  },
337
- )
328
+ resolveFilterValue: (val: [any, any]) => {
329
+ const [unsafeMin, unsafeMax] = val
330
+
331
+ const parsedMin = toDateTimestamp(unsafeMin)
332
+ const parsedMax = toDateTimestamp(unsafeMax)
333
+
334
+ let min = Number.isNaN(parsedMin) ? -Infinity : parsedMin
335
+ let max = Number.isNaN(parsedMax) ? Infinity : parsedMax
336
+
337
+ if (min > max) {
338
+ const temp = min
339
+ min = max
340
+ max = temp
341
+ }
342
+
343
+ return [min, max] as const
344
+ },
345
+ resolveDataValue: (val: any) => toDateTimestamp(val),
346
+ autoRemove: (val: any) =>
347
+ testFalsy(val) ||
348
+ (Array.isArray(val) && testFalsy(val[0]) && testFalsy(val[1])),
349
+ })
338
350
 
339
351
  // Array filters
340
352
 
341
353
  /**
342
354
  * Keeps rows whose scalar column value equals at least one filter value.
343
355
  */
344
- export const filterFn_arrHas = Object.assign(
345
- <TFeatures extends TableFeatures, TData extends RowData>(
346
- row: Row<TFeatures, TData>,
347
- columnId: string,
348
- filterValue: Array<unknown>,
349
- ) => {
350
- const value = row.getValue<unknown>(columnId)
356
+ export const filterFn_arrHas = constructFilterFn({
357
+ filter: (dataValue, filterValue: Array<unknown>) => {
351
358
  for (let i = 0; i < filterValue.length; i++) {
352
- if (value === filterValue[i]) {
359
+ if (dataValue === filterValue[i]) {
353
360
  return true
354
361
  }
355
362
  }
356
363
  return false
357
364
  },
358
- { autoRemove: (val: any) => testFalsy(val) || !val?.length },
359
- )
365
+ autoRemove: (val: any) => testFalsy(val) || !val?.length,
366
+ })
360
367
 
361
368
  /**
362
369
  * Keeps rows whose array or string column value includes at least one filter value.
363
370
  */
364
- export const filterFn_arrIncludes = Object.assign(
365
- <TFeatures extends TableFeatures, TData extends RowData>(
366
- row: Row<TFeatures, TData>,
367
- columnId: string,
368
- filterValue: Array<unknown>,
369
- ) => {
370
- const value = row.getValue<unknown>(columnId)
371
- if (typeof value !== 'string' && !Array.isArray(value)) {
371
+ export const filterFn_arrIncludes = constructFilterFn({
372
+ filter: (dataValue, filterValue: Array<unknown>) => {
373
+ if (typeof dataValue !== 'string' && !Array.isArray(dataValue)) {
372
374
  return false
373
375
  }
374
376
 
375
377
  for (let i = 0; i < filterValue.length; i++) {
376
- if (value.includes(filterValue[i] as any)) {
378
+ if (dataValue.includes(filterValue[i] as any)) {
377
379
  return true
378
380
  }
379
381
  }
380
382
  return false
381
383
  },
382
- { autoRemove: (val: any) => testFalsy(val) || !val?.length },
383
- )
384
+ autoRemove: (val: any) => testFalsy(val) || !val?.length,
385
+ })
384
386
 
385
387
  /**
386
388
  * Keeps rows whose array column value includes every filter value.
387
389
  */
388
- export const filterFn_arrIncludesAll = Object.assign(
389
- <TFeatures extends TableFeatures, TData extends RowData>(
390
- row: Row<TFeatures, TData>,
391
- columnId: string,
392
- filterValue: Array<unknown>,
393
- ) => {
394
- const value = row.getValue<Array<unknown>>(columnId)
395
- if (!Array.isArray(value)) return false
390
+ export const filterFn_arrIncludesAll = constructFilterFn({
391
+ filter: (dataValue, filterValue: Array<unknown>) => {
392
+ if (!Array.isArray(dataValue)) return false
396
393
  for (let i = 0; i < filterValue.length; i++) {
397
- if (!value.includes(filterValue[i])) {
394
+ if (!dataValue.includes(filterValue[i])) {
398
395
  return false
399
396
  }
400
397
  }
401
398
  return true
402
399
  },
403
- { autoRemove: (val: any) => testFalsy(val) || !val?.length },
404
- )
400
+ autoRemove: (val: any) => testFalsy(val) || !val?.length,
401
+ })
405
402
 
406
403
  /**
407
404
  * Keeps rows whose array column value includes at least one filter value.
408
405
  */
409
- export const filterFn_arrIncludesSome = Object.assign(
410
- <TFeatures extends TableFeatures, TData extends RowData>(
411
- row: Row<TFeatures, TData>,
412
- columnId: string,
413
- filterValue: Array<unknown>,
414
- ) => {
415
- const value = row.getValue<Array<unknown>>(columnId)
416
- if (!Array.isArray(value)) return false
406
+ export const filterFn_arrIncludesSome = constructFilterFn({
407
+ filter: (dataValue, filterValue: Array<unknown>) => {
408
+ if (!Array.isArray(dataValue)) return false
417
409
  for (let i = 0; i < filterValue.length; i++) {
418
- if (value.includes(filterValue[i])) {
410
+ if (dataValue.includes(filterValue[i])) {
419
411
  return true
420
412
  }
421
413
  }
422
414
  return false
423
415
  },
424
- { autoRemove: (val: any) => testFalsy(val) || !val?.length },
425
- )
416
+ autoRemove: (val: any) => testFalsy(val) || !val?.length,
417
+ })
426
418
 
427
419
  // Export
428
420
 
429
421
  /**
430
422
  * The built-in filter function registry.
431
423
  *
432
- * Pass this object to filtered row model creation or extend it with custom filter functions.
424
+ * Registering this full object opts out of tree-shaking: every built-in
425
+ * filter function ends up in your bundle. Prefer importing the `filterFn_*`
426
+ * functions you actually use and registering just those in the `filterFns`
427
+ * slot, or passing them directly to the `filterFn` column option.
428
+ *
429
+ * @deprecated Import individual `filterFn_*` functions instead for a smaller
430
+ * bundle. This export still works and is not going away in v9, but built-in
431
+ * name resolution (including `filterFn: 'auto'`) only finds functions you
432
+ * register yourself.
433
433
  */
434
434
  export const filterFns = {
435
435
  arrIncludes: filterFn_arrIncludes,
@@ -438,11 +438,17 @@ export const filterFns = {
438
438
  arrIncludesSome: filterFn_arrIncludesSome,
439
439
  between: filterFn_between,
440
440
  betweenInclusive: filterFn_betweenInclusive,
441
+ empty: filterFn_empty,
442
+ endsWith: filterFn_endsWith,
441
443
  equals: filterFn_equals,
442
444
  equalsString: filterFn_equalsString,
445
+ equalsStringSensitive: filterFn_equalsStringSensitive,
446
+ inDateRange: filterFn_inDateRange,
443
447
  inNumberRange: filterFn_inNumberRange,
444
448
  includesString: filterFn_includesString,
445
449
  includesStringSensitive: filterFn_includesStringSensitive,
450
+ notEmpty: filterFn_notEmpty,
451
+ startsWith: filterFn_startsWith,
446
452
  weakEquals: filterFn_weakEquals,
447
453
  }
448
454
 
@@ -453,3 +459,75 @@ export type BuiltInFilterFn = keyof typeof filterFns
453
459
  function testFalsy(val: any) {
454
460
  return val === undefined || val === null || val === ''
455
461
  }
462
+
463
+ function testValueEmpty(dataValue: any) {
464
+ return dataValue == null || String(dataValue).trim() === ''
465
+ }
466
+
467
+ function toDateTimestamp(value: any): number {
468
+ if (value instanceof Date) {
469
+ return value.getTime()
470
+ }
471
+ if (typeof value === 'number') {
472
+ return value
473
+ }
474
+ // Guard nullish and blank values explicitly: `new Date(null)` is the epoch,
475
+ // not an invalid date.
476
+ if (value == null || value === '') {
477
+ return NaN
478
+ }
479
+ return new Date(value).getTime()
480
+ }
481
+
482
+ function compareGreaterThan(dataValue: any, filterValue: any): boolean {
483
+ const numericDataValue = dataValue == null ? 0 : +dataValue
484
+ const numericFilterValue = Number(filterValue)
485
+
486
+ if (!isNaN(numericFilterValue) && !isNaN(numericDataValue)) {
487
+ return numericDataValue > numericFilterValue
488
+ }
489
+
490
+ const stringDataValue = String(dataValue ?? '')
491
+ .toLowerCase()
492
+ .trim()
493
+ const stringFilterValue = String(filterValue).toLowerCase().trim()
494
+ return stringDataValue > stringFilterValue
495
+ }
496
+
497
+ function compareGreaterThanOrEqualTo(dataValue: any, filterValue: any) {
498
+ return dataValue === filterValue || compareGreaterThan(dataValue, filterValue)
499
+ }
500
+
501
+ function compareBetween(
502
+ dataValue: any,
503
+ filterValues: [unknown, unknown],
504
+ inclusive: boolean,
505
+ ): boolean {
506
+ const min = filterValues[0]
507
+ const hasMin = min !== '' && min !== undefined
508
+ if (hasMin) {
509
+ const passesMin = inclusive
510
+ ? compareGreaterThanOrEqualTo(dataValue, min)
511
+ : compareGreaterThan(dataValue, min)
512
+ if (!passesMin) {
513
+ return false
514
+ }
515
+ }
516
+
517
+ const max = filterValues[1]
518
+ if (max === '' || max === undefined) {
519
+ return true
520
+ }
521
+
522
+ if (hasMin) {
523
+ const numericMin = Number(min)
524
+ const numericMax = Number(max)
525
+ if (!isNaN(numericMin) && !isNaN(numericMax) && numericMin > numericMax) {
526
+ return true
527
+ }
528
+ }
529
+
530
+ return inclusive
531
+ ? !compareGreaterThan(dataValue, max)
532
+ : !compareGreaterThanOrEqualTo(dataValue, max)
533
+ }