@contember/bindx-dataview 0.1.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 (114) hide show
  1. package/dist/DataGrid.d.ts +38 -0
  2. package/dist/DataGrid.d.ts.map +1 -0
  3. package/dist/DataViewContext.d.ts +39 -0
  4. package/dist/DataViewContext.d.ts.map +1 -0
  5. package/dist/DataViewKeyProvider.d.ts +8 -0
  6. package/dist/DataViewKeyProvider.d.ts.map +1 -0
  7. package/dist/HasManyDataGrid.d.ts +51 -0
  8. package/dist/HasManyDataGrid.d.ts.map +1 -0
  9. package/dist/columnLeaf.d.ts +72 -0
  10. package/dist/columnLeaf.d.ts.map +1 -0
  11. package/dist/columnTypes.d.ts +35 -0
  12. package/dist/columnTypes.d.ts.map +1 -0
  13. package/dist/columns.d.ts +115 -0
  14. package/dist/columns.d.ts.map +1 -0
  15. package/dist/contextHooks.d.ts +45 -0
  16. package/dist/contextHooks.d.ts.map +1 -0
  17. package/dist/createColumn.d.ts +37 -0
  18. package/dist/createColumn.d.ts.map +1 -0
  19. package/dist/createRelationColumn.d.ts +57 -0
  20. package/dist/createRelationColumn.d.ts.map +1 -0
  21. package/dist/dataAttribute.d.ts +6 -0
  22. package/dist/dataAttribute.d.ts.map +1 -0
  23. package/dist/export.d.ts +46 -0
  24. package/dist/export.d.ts.map +1 -0
  25. package/dist/filterComponents.d.ts +127 -0
  26. package/dist/filterComponents.d.ts.map +1 -0
  27. package/dist/filterContext.d.ts +10 -0
  28. package/dist/filterContext.d.ts.map +1 -0
  29. package/dist/filterHooks.d.ts +74 -0
  30. package/dist/filterHooks.d.ts.map +1 -0
  31. package/dist/filterScope.d.ts +54 -0
  32. package/dist/filterScope.d.ts.map +1 -0
  33. package/dist/filterWrappers.d.ts +66 -0
  34. package/dist/filterWrappers.d.ts.map +1 -0
  35. package/dist/index.d.ts +28 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/infiniteLoad.d.ts +32 -0
  38. package/dist/infiniteLoad.d.ts.map +1 -0
  39. package/dist/loaderComponents.d.ts +75 -0
  40. package/dist/loaderComponents.d.ts.map +1 -0
  41. package/dist/markers.d.ts +36 -0
  42. package/dist/markers.d.ts.map +1 -0
  43. package/dist/pagingComponents.d.ts +50 -0
  44. package/dist/pagingComponents.d.ts.map +1 -0
  45. package/dist/renderCellValue.d.ts +9 -0
  46. package/dist/renderCellValue.d.ts.map +1 -0
  47. package/dist/select/MultiSelect.d.ts +27 -0
  48. package/dist/select/MultiSelect.d.ts.map +1 -0
  49. package/dist/select/Select.d.ts +27 -0
  50. package/dist/select/Select.d.ts.map +1 -0
  51. package/dist/select/SelectDataView.d.ts +46 -0
  52. package/dist/select/SelectDataView.d.ts.map +1 -0
  53. package/dist/select/SelectEachValue.d.ts +20 -0
  54. package/dist/select/SelectEachValue.d.ts.map +1 -0
  55. package/dist/select/SelectItemTrigger.d.ts +25 -0
  56. package/dist/select/SelectItemTrigger.d.ts.map +1 -0
  57. package/dist/select/SelectOption.d.ts +23 -0
  58. package/dist/select/SelectOption.d.ts.map +1 -0
  59. package/dist/select/SelectPlaceholder.d.ts +16 -0
  60. package/dist/select/SelectPlaceholder.d.ts.map +1 -0
  61. package/dist/select/index.d.ts +9 -0
  62. package/dist/select/index.d.ts.map +1 -0
  63. package/dist/select/selectContext.d.ts +20 -0
  64. package/dist/select/selectContext.d.ts.map +1 -0
  65. package/dist/selectionComponents.d.ts +62 -0
  66. package/dist/selectionComponents.d.ts.map +1 -0
  67. package/dist/sortingComponents.d.ts +39 -0
  68. package/dist/sortingComponents.d.ts.map +1 -0
  69. package/dist/stateStorage.d.ts +20 -0
  70. package/dist/stateStorage.d.ts.map +1 -0
  71. package/dist/useDataGridSetup.d.ts +53 -0
  72. package/dist/useDataGridSetup.d.ts.map +1 -0
  73. package/dist/useDataView.d.ts +57 -0
  74. package/dist/useDataView.d.ts.map +1 -0
  75. package/dist/useDataViewState.d.ts +89 -0
  76. package/dist/useDataViewState.d.ts.map +1 -0
  77. package/package.json +33 -0
  78. package/src/DataGrid.tsx +173 -0
  79. package/src/DataViewContext.tsx +56 -0
  80. package/src/DataViewKeyProvider.tsx +16 -0
  81. package/src/HasManyDataGrid.tsx +319 -0
  82. package/src/columnLeaf.ts +148 -0
  83. package/src/columnTypes.ts +160 -0
  84. package/src/columns.tsx +306 -0
  85. package/src/contextHooks.ts +157 -0
  86. package/src/createColumn.ts +128 -0
  87. package/src/createRelationColumn.tsx +203 -0
  88. package/src/dataAttribute.ts +7 -0
  89. package/src/export.tsx +219 -0
  90. package/src/filterComponents.tsx +617 -0
  91. package/src/filterContext.ts +27 -0
  92. package/src/filterHooks.ts +386 -0
  93. package/src/filterScope.tsx +89 -0
  94. package/src/filterWrappers.tsx +187 -0
  95. package/src/index.ts +356 -0
  96. package/src/infiniteLoad.tsx +188 -0
  97. package/src/loaderComponents.tsx +239 -0
  98. package/src/markers.ts +54 -0
  99. package/src/pagingComponents.tsx +149 -0
  100. package/src/select/MultiSelect.tsx +88 -0
  101. package/src/select/Select.tsx +88 -0
  102. package/src/select/SelectDataView.tsx +231 -0
  103. package/src/select/SelectEachValue.tsx +34 -0
  104. package/src/select/SelectItemTrigger.tsx +51 -0
  105. package/src/select/SelectOption.tsx +40 -0
  106. package/src/select/SelectPlaceholder.tsx +22 -0
  107. package/src/select/index.ts +17 -0
  108. package/src/select/selectContext.ts +82 -0
  109. package/src/selectionComponents.tsx +160 -0
  110. package/src/sortingComponents.tsx +104 -0
  111. package/src/stateStorage.ts +188 -0
  112. package/src/useDataGridSetup.ts +230 -0
  113. package/src/useDataView.ts +148 -0
  114. package/src/useDataViewState.ts +404 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Filter hooks — typed state access and action hooks for filter components.
3
+ *
4
+ * These hooks match the Contember react-dataview API:
5
+ * - useDataViewFilter: generic [state, setFilter, meta] tuple
6
+ * - useDataViewBooleanFilter: [current, set] with action types
7
+ * - useDataViewEnumFilter: [current, set] with include/exclude/toggle actions
8
+ * - useDataViewRelationFilter: [current, set] with include/exclude/toggle actions
9
+ * - useDataViewTextFilterInput: { value, onChange } with debounce
10
+ * - useDataViewTextFilterMatchMode: [isCurrent, set]
11
+ * - useDataViewNullFilter: [state, set] with include/exclude/toggle actions
12
+ */
13
+
14
+ import { type ChangeEvent, type SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'
15
+ import { useDataViewContext } from './DataViewContext.js'
16
+ import type {
17
+ FilterArtifact,
18
+ TextFilterArtifact,
19
+ BooleanFilterArtifact,
20
+ EnumFilterArtifact,
21
+ RelationFilterArtifact,
22
+ NumberRangeFilterArtifact,
23
+ } from '@contember/bindx'
24
+
25
+ // ============================================================================
26
+ // Generic useDataViewFilter — matches Contember's [state, setFilter, meta] API
27
+ // ============================================================================
28
+
29
+ export type UseDataViewFilterResult<T extends FilterArtifact> = [
30
+ state: T | undefined,
31
+ setFilter: (filter: SetStateAction<T | undefined>) => void,
32
+ meta: { isEmpty?: boolean },
33
+ ]
34
+
35
+ export function useDataViewFilter<T extends FilterArtifact>(key: string): UseDataViewFilterResult<T> {
36
+ const { filtering } = useDataViewContext()
37
+ const state = filtering.getArtifact(key) as T | undefined
38
+ const filter = filtering.filters.get(key)
39
+ const isEmpty = state ? !filter?.handler.isActive(state) : true
40
+
41
+ const setFilter = useCallback(
42
+ (filterOrUpdater: SetStateAction<T | undefined>): void => {
43
+ if (typeof filterOrUpdater === 'function') {
44
+ const current = filtering.getArtifact(key) as T | undefined
45
+ const next = (filterOrUpdater as (prev: T | undefined) => T | undefined)(current)
46
+ if (next !== undefined) {
47
+ filtering.setArtifact(key, next)
48
+ } else {
49
+ filtering.resetFilter(key)
50
+ }
51
+ } else if (filterOrUpdater !== undefined) {
52
+ filtering.setArtifact(key, filterOrUpdater)
53
+ } else {
54
+ filtering.resetFilter(key)
55
+ }
56
+ },
57
+ [filtering, key],
58
+ )
59
+
60
+ return [state, setFilter, { isEmpty }]
61
+ }
62
+
63
+ // ============================================================================
64
+ // Boolean Filter Hook
65
+ // ============================================================================
66
+
67
+ export type DataViewSetBooleanFilterAction = 'include' | 'unset' | 'toggle'
68
+ export type DataViewBooleanFilterCurrent = 'include' | 'none'
69
+ export type UseDataViewBooleanFilterResult = [
70
+ current: DataViewBooleanFilterCurrent,
71
+ set: (action: DataViewSetBooleanFilterAction) => void,
72
+ ]
73
+
74
+ export function useDataViewBooleanFilter(name: string, value: boolean): UseDataViewBooleanFilterResult {
75
+ const factory = useDataViewBooleanFilterFactory(name)
76
+ return useMemo(() => factory(value), [factory, value])
77
+ }
78
+
79
+ export function useDataViewBooleanFilterFactory(name: string): (value: boolean) => UseDataViewBooleanFilterResult {
80
+ const [filter, setFilter] = useDataViewFilter<BooleanFilterArtifact>(name)
81
+
82
+ return useCallback((value: boolean): UseDataViewBooleanFilterResult => {
83
+ const key = value ? 'includeTrue' : 'includeFalse'
84
+ const current: DataViewBooleanFilterCurrent = filter?.[key] ? 'include' : 'none'
85
+
86
+ const set = (action: DataViewSetBooleanFilterAction = 'include'): void => {
87
+ switch (action) {
88
+ case 'unset':
89
+ setFilter(it => ({ ...it, [key]: undefined }))
90
+ return
91
+ case 'toggle':
92
+ setFilter(it => ({ ...it, [key]: it?.[key] === true ? undefined : true }))
93
+ return
94
+ case 'include':
95
+ setFilter(it => ({ ...it, [key]: true }))
96
+ return
97
+ }
98
+ }
99
+
100
+ return [current, set]
101
+ }, [filter, setFilter])
102
+ }
103
+
104
+ // ============================================================================
105
+ // Enum Filter Hook
106
+ // ============================================================================
107
+
108
+ export type DataViewSetEnumFilterAction = 'include' | 'exclude' | 'unset' | 'toggleInclude' | 'toggleExclude'
109
+ export type DataViewEnumFilterCurrent = 'include' | 'exclude' | 'none'
110
+ export type UseDataViewEnumFilterResult = [
111
+ current: DataViewEnumFilterCurrent,
112
+ set: (action: DataViewSetEnumFilterAction) => void,
113
+ ]
114
+
115
+ export function useDataViewEnumFilter(name: string, value: string): UseDataViewEnumFilterResult {
116
+ const factory = useDataViewEnumFilterFactory(name)
117
+ return useMemo(() => factory(value), [factory, value])
118
+ }
119
+
120
+ export function useDataViewEnumFilterFactory(name: string): (value: string) => UseDataViewEnumFilterResult {
121
+ const [filter, setFilter] = useDataViewFilter<EnumFilterArtifact>(name)
122
+
123
+ return useCallback((value: string): UseDataViewEnumFilterResult => {
124
+ const current: DataViewEnumFilterCurrent = (() => {
125
+ if (filter?.values?.includes(value)) return 'include'
126
+ if (filter?.notValues?.includes(value)) return 'exclude'
127
+ return 'none'
128
+ })()
129
+
130
+ const set = (action: DataViewSetEnumFilterAction = 'include'): void => {
131
+ switch (action) {
132
+ case 'unset':
133
+ setFilter(it => ({
134
+ ...it,
135
+ values: it?.values?.filter(v => v !== value),
136
+ notValues: it?.notValues?.filter(v => v !== value),
137
+ }))
138
+ return
139
+ case 'toggleInclude':
140
+ setFilter(it => ({
141
+ ...it,
142
+ values: it?.values?.includes(value)
143
+ ? it.values.filter(v => v !== value)
144
+ : [...(it?.values ?? []), value],
145
+ notValues: it?.notValues?.filter(v => v !== value),
146
+ }))
147
+ return
148
+ case 'toggleExclude':
149
+ setFilter(it => ({
150
+ ...it,
151
+ notValues: it?.notValues?.includes(value)
152
+ ? it.notValues.filter(v => v !== value)
153
+ : [...(it?.notValues ?? []), value],
154
+ values: it?.values?.filter(v => v !== value),
155
+ }))
156
+ return
157
+ case 'include':
158
+ setFilter(it => ({
159
+ ...it,
160
+ values: [...(it?.values ?? []), value],
161
+ notValues: it?.notValues?.filter(v => v !== value),
162
+ }))
163
+ return
164
+ case 'exclude':
165
+ setFilter(it => ({
166
+ ...it,
167
+ notValues: [...(it?.notValues ?? []), value],
168
+ values: it?.values?.filter(v => v !== value),
169
+ }))
170
+ return
171
+ }
172
+ }
173
+
174
+ return [current, set]
175
+ }, [filter?.values, filter?.notValues, setFilter])
176
+ }
177
+
178
+ // ============================================================================
179
+ // Relation Filter Hook
180
+ // ============================================================================
181
+
182
+ export type DataViewSetRelationFilterAction = 'include' | 'exclude' | 'unset' | 'toggleInclude' | 'toggleExclude'
183
+ export type DataViewRelationFilterCurrent = 'include' | 'exclude' | 'none'
184
+ export type UseDataViewRelationFilterResult = [
185
+ current: DataViewRelationFilterCurrent,
186
+ set: (action: DataViewSetRelationFilterAction) => void,
187
+ ]
188
+
189
+ export function useDataViewRelationFilter(name: string, entityId: string): UseDataViewRelationFilterResult {
190
+ const factory = useDataViewRelationFilterFactory(name)
191
+ return useMemo(() => factory(entityId), [factory, entityId])
192
+ }
193
+
194
+ export function useDataViewRelationFilterFactory(name: string): (id: string) => UseDataViewRelationFilterResult {
195
+ const [filter, setFilter] = useDataViewFilter<RelationFilterArtifact>(name)
196
+
197
+ return useCallback((id: string): UseDataViewRelationFilterResult => {
198
+ const current: DataViewRelationFilterCurrent = (() => {
199
+ if (filter?.id?.includes(id)) return 'include'
200
+ if (filter?.notId?.includes(id)) return 'exclude'
201
+ return 'none'
202
+ })()
203
+
204
+ const set = (action: DataViewSetRelationFilterAction = 'include'): void => {
205
+ switch (action) {
206
+ case 'unset':
207
+ setFilter(it => ({
208
+ ...it,
209
+ id: it?.id?.filter(v => v !== id),
210
+ notId: it?.notId?.filter(v => v !== id),
211
+ }))
212
+ return
213
+ case 'toggleInclude':
214
+ setFilter(it => ({
215
+ ...it,
216
+ id: it?.id?.includes(id)
217
+ ? it.id.filter(v => v !== id)
218
+ : [...(it?.id ?? []), id],
219
+ notId: it?.notId?.filter(v => v !== id),
220
+ }))
221
+ return
222
+ case 'toggleExclude':
223
+ setFilter(it => ({
224
+ ...it,
225
+ notId: it?.notId?.includes(id)
226
+ ? it.notId.filter(v => v !== id)
227
+ : [...(it?.notId ?? []), id],
228
+ id: it?.id?.filter(v => v !== id),
229
+ }))
230
+ return
231
+ case 'include':
232
+ setFilter(it => ({
233
+ ...it,
234
+ id: [...(it?.id ?? []), id],
235
+ notId: it?.notId?.filter(v => v !== id),
236
+ }))
237
+ return
238
+ case 'exclude':
239
+ setFilter(it => ({
240
+ ...it,
241
+ notId: [...(it?.notId ?? []), id],
242
+ id: it?.id?.filter(v => v !== id),
243
+ }))
244
+ return
245
+ }
246
+ }
247
+
248
+ return [current, set]
249
+ }, [filter?.id, filter?.notId, setFilter])
250
+ }
251
+
252
+ // ============================================================================
253
+ // Text Filter Input Hook
254
+ // ============================================================================
255
+
256
+ export interface UseDataViewTextFilterInputResult {
257
+ value: string
258
+ onChange: (e: ChangeEvent<HTMLInputElement>) => void
259
+ }
260
+
261
+ export function useDataViewTextFilterInput({ name, debounceMs = 500 }: {
262
+ name: string
263
+ debounceMs?: number
264
+ }): UseDataViewTextFilterInputResult {
265
+ const [state, setFilter] = useDataViewFilter<TextFilterArtifact>(name)
266
+ const [value, setValue] = useState(state?.query ?? '')
267
+ const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
268
+
269
+ useEffect(() => {
270
+ if (!timerRef.current) {
271
+ setValue(state?.query ?? '')
272
+ }
273
+ }, [state?.query])
274
+
275
+ const onChange = useCallback((e: ChangeEvent<HTMLInputElement>): void => {
276
+ const newValue = e.target.value
277
+ if (debounceMs && newValue) {
278
+ if (timerRef.current) clearTimeout(timerRef.current)
279
+ timerRef.current = setTimeout(() => {
280
+ timerRef.current = undefined
281
+ setFilter(it => ({ mode: 'contains' as const, ...it, query: newValue }))
282
+ }, debounceMs)
283
+ } else {
284
+ setFilter(it => ({ mode: 'contains' as const, ...it, query: newValue }))
285
+ }
286
+ setValue(newValue)
287
+ }, [debounceMs, setFilter])
288
+
289
+ return { value, onChange }
290
+ }
291
+
292
+ // ============================================================================
293
+ // Text Filter Match Mode Hook
294
+ // ============================================================================
295
+
296
+ export type UseDataViewTextFilterMatchModeResult = [isCurrent: boolean, set: () => void]
297
+
298
+ export function useDataViewTextFilterMatchMode(
299
+ name: string,
300
+ mode: TextFilterArtifact['mode'],
301
+ ): UseDataViewTextFilterMatchModeResult {
302
+ const [state, setFilter] = useDataViewFilter<TextFilterArtifact>(name)
303
+
304
+ const cb = useCallback((): void => {
305
+ setFilter(it => ({ query: '', ...it, mode }))
306
+ }, [setFilter, mode])
307
+
308
+ return [state?.mode === mode, cb]
309
+ }
310
+
311
+ // ============================================================================
312
+ // Number Filter Input Hook
313
+ // ============================================================================
314
+
315
+ export interface UseDataViewNumberFilterInputResult {
316
+ value: string
317
+ onChange: (e: ChangeEvent<HTMLInputElement>) => void
318
+ }
319
+
320
+ export interface UseDataViewNumberFilterInputProps {
321
+ name: string
322
+ type: 'from' | 'to'
323
+ allowFloat?: boolean
324
+ }
325
+
326
+ export function useDataViewNumberFilterInput({ name, type, allowFloat }: UseDataViewNumberFilterInputProps): UseDataViewNumberFilterInputResult {
327
+ const [state, setFilter] = useDataViewFilter<NumberRangeFilterArtifact>(name)
328
+
329
+ const onChange = useCallback((e: ChangeEvent<HTMLInputElement>): void => {
330
+ const raw = e.target.value
331
+ const num = raw === '' ? null : (allowFloat ? parseFloat(raw) : parseInt(raw, 10))
332
+ const parsed = num !== null && isNaN(num) ? null : num
333
+ if (type === 'from') {
334
+ setFilter(it => ({ ...it, min: parsed, max: it?.max ?? null }))
335
+ } else {
336
+ setFilter(it => ({ ...it, min: it?.min ?? null, max: parsed }))
337
+ }
338
+ }, [type, allowFloat, setFilter])
339
+
340
+ const value = type === 'from' ? state?.min : state?.max
341
+
342
+ return { value: value?.toString() ?? '', onChange }
343
+ }
344
+
345
+ // ============================================================================
346
+ // Null Filter Hook
347
+ // ============================================================================
348
+
349
+ export type DataViewSetNullFilterAction = 'include' | 'exclude' | 'unset' | 'toggleInclude' | 'toggleExclude'
350
+ export type DataViewNullFilterState = 'include' | 'exclude' | 'none'
351
+ export type UseDataViewNullFilterResult = [
352
+ state: DataViewNullFilterState,
353
+ set: (action: DataViewSetNullFilterAction) => void,
354
+ ]
355
+
356
+ export function useDataViewNullFilter(name: string): UseDataViewNullFilterResult {
357
+ const [state, setFilter] = useDataViewFilter<{ nullCondition?: boolean } & FilterArtifact>(name)
358
+
359
+ const cb = useCallback((action: DataViewSetNullFilterAction): void => {
360
+ switch (action) {
361
+ case 'unset':
362
+ setFilter(it => ({ ...it, nullCondition: undefined }))
363
+ return
364
+ case 'toggleInclude':
365
+ setFilter(it => ({ ...it, nullCondition: it?.nullCondition === true ? undefined : true }))
366
+ return
367
+ case 'toggleExclude':
368
+ setFilter(it => ({ ...it, nullCondition: it?.nullCondition === false ? undefined : false }))
369
+ return
370
+ case 'include':
371
+ setFilter(it => ({ ...it, nullCondition: true }))
372
+ return
373
+ case 'exclude':
374
+ setFilter(it => ({ ...it, nullCondition: false }))
375
+ return
376
+ }
377
+ }, [setFilter])
378
+
379
+ const currentState: DataViewNullFilterState = state?.nullCondition === true
380
+ ? 'include'
381
+ : state?.nullCondition === false
382
+ ? 'exclude'
383
+ : 'none'
384
+
385
+ return [currentState, cb]
386
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Filter scope components — declarative filter registration and name scoping.
3
+ *
4
+ * Usage:
5
+ * ```tsx
6
+ * <DataViewFilterScope name="title">
7
+ * <DataViewTextFilterInput />
8
+ * </DataViewFilterScope>
9
+ *
10
+ * <DataViewFilter name="title" filterHandler={createTextFilterHandler('title')}>
11
+ * <DataViewTextFilterInput />
12
+ * </DataViewFilter>
13
+ *
14
+ * <DataViewTextFilter field="title">
15
+ * <DataViewTextFilterInput />
16
+ * </DataViewTextFilter>
17
+ *
18
+ * <DataViewHasFilterType name="title">
19
+ * <span>Title filter is available</span>
20
+ * </DataViewHasFilterType>
21
+ * ```
22
+ */
23
+
24
+ import React, { type ReactElement } from 'react'
25
+ import { DataViewFilterNameProvider } from './filterContext.js'
26
+ import { useDataViewContext } from './DataViewContext.js'
27
+ import type { FilterHandler, FilterArtifact } from '@contember/bindx'
28
+
29
+ // ============================================================================
30
+ // DataViewFilterScope
31
+ // ============================================================================
32
+
33
+ export interface DataViewFilterScopeProps {
34
+ name: string
35
+ children: React.ReactNode
36
+ }
37
+
38
+ /**
39
+ * Sets the filter name context for all children.
40
+ * Child filter triggers will infer their filter name from this scope.
41
+ */
42
+ export function DataViewFilterScope({ name, children }: DataViewFilterScopeProps): ReactElement {
43
+ return (
44
+ <DataViewFilterNameProvider value={name}>
45
+ {children}
46
+ </DataViewFilterNameProvider>
47
+ )
48
+ }
49
+
50
+ // ============================================================================
51
+ // DataViewFilter
52
+ // ============================================================================
53
+
54
+ export interface DataViewFilterProps {
55
+ name: string
56
+ filterHandler: FilterHandler<FilterArtifact>
57
+ children?: React.ReactNode
58
+ }
59
+
60
+ /**
61
+ * Registers a filter handler and sets the filter name context.
62
+ *
63
+ * Note: When used inside a DataGrid, filters are already registered
64
+ * from column definitions. This component then just provides the name scope.
65
+ */
66
+ export function DataViewFilter({ name, children }: DataViewFilterProps): ReactElement {
67
+ return (
68
+ <DataViewFilterNameProvider value={name}>
69
+ {children}
70
+ </DataViewFilterNameProvider>
71
+ )
72
+ }
73
+
74
+ // ============================================================================
75
+ // DataViewHasFilterType
76
+ // ============================================================================
77
+
78
+ export interface DataViewHasFilterTypeProps {
79
+ name: string
80
+ children: React.ReactNode
81
+ }
82
+
83
+ /**
84
+ * Renders children only if a filter with the given name is registered.
85
+ */
86
+ export function DataViewHasFilterType({ name, children }: DataViewHasFilterTypeProps): ReactElement | null {
87
+ const { filtering } = useDataViewContext()
88
+ return filtering.filters.has(name) ? <>{children}</> : null
89
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Typed filter wrapper components — provide filter name scope for specific filter types.
3
+ *
4
+ * Each wrapper sets the filter name context so child trigger components
5
+ * can infer their filter name automatically.
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * <DataViewTextFilter field={it.title}>
10
+ * <DataViewTextFilterInput />
11
+ * <DataViewTextFilterMatchModeTrigger mode="contains">
12
+ * <button>Contains</button>
13
+ * </DataViewTextFilterMatchModeTrigger>
14
+ * </DataViewTextFilter>
15
+ *
16
+ * <DataViewBooleanFilter field={it.published}>
17
+ * <DataViewBooleanFilterTrigger value={true} action="toggle">
18
+ * <button>Published</button>
19
+ * </DataViewBooleanFilterTrigger>
20
+ * </DataViewBooleanFilter>
21
+ * ```
22
+ */
23
+
24
+ import React, { type ReactElement } from 'react'
25
+ import type { FieldRefBase } from '@contember/bindx'
26
+ import { FIELD_REF_META } from '@contember/bindx'
27
+ import { DataViewFilterNameProvider } from './filterContext.js'
28
+
29
+ // ============================================================================
30
+ // Common props
31
+ // ============================================================================
32
+
33
+ interface FilterWrapperProps<T> {
34
+ field: FieldRefBase<T>
35
+ name?: string
36
+ children: React.ReactNode
37
+ }
38
+
39
+ function resolveFilterName<T>(name: string | undefined, field: FieldRefBase<T>): string {
40
+ return name ?? field[FIELD_REF_META].fieldName
41
+ }
42
+
43
+ // ============================================================================
44
+ // DataViewTextFilter
45
+ // ============================================================================
46
+
47
+ export interface DataViewTextFilterProps<T> extends FilterWrapperProps<T> {}
48
+
49
+ export function DataViewTextFilter<T>({ field, name, children }: DataViewTextFilterProps<T>): ReactElement {
50
+ return (
51
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
52
+ {children}
53
+ </DataViewFilterNameProvider>
54
+ )
55
+ }
56
+
57
+ // ============================================================================
58
+ // DataViewBooleanFilter
59
+ // ============================================================================
60
+
61
+ export interface DataViewBooleanFilterProps<T> extends FilterWrapperProps<T> {}
62
+
63
+ export function DataViewBooleanFilter<T>({ field, name, children }: DataViewBooleanFilterProps<T>): ReactElement {
64
+ return (
65
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
66
+ {children}
67
+ </DataViewFilterNameProvider>
68
+ )
69
+ }
70
+
71
+ // ============================================================================
72
+ // DataViewNumberFilter
73
+ // ============================================================================
74
+
75
+ export interface DataViewNumberFilterProps<T> extends FilterWrapperProps<T> {}
76
+
77
+ export function DataViewNumberFilter<T>({ field, name, children }: DataViewNumberFilterProps<T>): ReactElement {
78
+ return (
79
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
80
+ {children}
81
+ </DataViewFilterNameProvider>
82
+ )
83
+ }
84
+
85
+ // ============================================================================
86
+ // DataViewDateFilter
87
+ // ============================================================================
88
+
89
+ export interface DataViewDateFilterProps<T> extends FilterWrapperProps<T> {}
90
+
91
+ export function DataViewDateFilter<T>({ field, name, children }: DataViewDateFilterProps<T>): ReactElement {
92
+ return (
93
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
94
+ {children}
95
+ </DataViewFilterNameProvider>
96
+ )
97
+ }
98
+
99
+ // ============================================================================
100
+ // DataViewEnumFilter
101
+ // ============================================================================
102
+
103
+ export interface DataViewEnumFilterProps<T> extends FilterWrapperProps<T> {}
104
+
105
+ export function DataViewEnumFilter<T>({ field, name, children }: DataViewEnumFilterProps<T>): ReactElement {
106
+ return (
107
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
108
+ {children}
109
+ </DataViewFilterNameProvider>
110
+ )
111
+ }
112
+
113
+ // ============================================================================
114
+ // DataViewEnumListFilter
115
+ // ============================================================================
116
+
117
+ export interface DataViewEnumListFilterProps<T> extends FilterWrapperProps<T> {}
118
+
119
+ export function DataViewEnumListFilter<T>({ field, name, children }: DataViewEnumListFilterProps<T>): ReactElement {
120
+ return (
121
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
122
+ {children}
123
+ </DataViewFilterNameProvider>
124
+ )
125
+ }
126
+
127
+ // ============================================================================
128
+ // DataViewHasOneFilter
129
+ // ============================================================================
130
+
131
+ export interface DataViewHasOneFilterProps<T> extends FilterWrapperProps<T> {}
132
+
133
+ export function DataViewHasOneFilter<T>({ field, name, children }: DataViewHasOneFilterProps<T>): ReactElement {
134
+ return (
135
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
136
+ {children}
137
+ </DataViewFilterNameProvider>
138
+ )
139
+ }
140
+
141
+ // ============================================================================
142
+ // DataViewHasManyFilter
143
+ // ============================================================================
144
+
145
+ export interface DataViewHasManyFilterProps<T> extends FilterWrapperProps<T> {}
146
+
147
+ export function DataViewHasManyFilter<T>({ field, name, children }: DataViewHasManyFilterProps<T>): ReactElement {
148
+ return (
149
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
150
+ {children}
151
+ </DataViewFilterNameProvider>
152
+ )
153
+ }
154
+
155
+ // ============================================================================
156
+ // DataViewIsDefinedFilter
157
+ // ============================================================================
158
+
159
+ export interface DataViewIsDefinedFilterProps<T> extends FilterWrapperProps<T> {}
160
+
161
+ export function DataViewIsDefinedFilter<T>({ field, name, children }: DataViewIsDefinedFilterProps<T>): ReactElement {
162
+ return (
163
+ <DataViewFilterNameProvider value={resolveFilterName(name, field)}>
164
+ {children}
165
+ </DataViewFilterNameProvider>
166
+ )
167
+ }
168
+
169
+ // ============================================================================
170
+ // DataViewUnionTextFilter
171
+ // ============================================================================
172
+
173
+ export interface DataViewUnionTextFilterProps<T> {
174
+ /** Single field or array of fields to search across */
175
+ fields: FieldRefBase<T> | FieldRefBase<T>[]
176
+ /** Required unique filter name */
177
+ name: string
178
+ children: React.ReactNode
179
+ }
180
+
181
+ export function DataViewUnionTextFilter<T>({ name, children }: DataViewUnionTextFilterProps<T>): ReactElement {
182
+ return (
183
+ <DataViewFilterNameProvider value={name}>
184
+ {children}
185
+ </DataViewFilterNameProvider>
186
+ )
187
+ }