@izumisy-tailor/tailor-data-viewer 0.3.1 → 0.3.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@izumisy-tailor/tailor-data-viewer",
3
3
  "private": false,
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "type": "module",
6
6
  "description": "Flexible data viewer component for Tailor Platform",
7
7
  "files": [
@@ -1,69 +1,66 @@
1
1
  import { createContext, useContext, type ReactNode } from "react";
2
- import type { UseCollectionReturn } from "../types";
2
+ import type { CollectionControl } from "../types";
3
3
 
4
- const CollectionVariablesContext = createContext<UseCollectionReturn<
5
- string,
6
- unknown
7
- > | null>(null);
4
+ const CollectionControlContext = createContext<CollectionControl | null>(null);
8
5
 
9
6
  /**
10
- * Provider that shares collection query parameters via React Context.
7
+ * Provider that shares collection control state via React Context.
11
8
  *
12
9
  * @example
13
10
  * ```tsx
14
- * const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
11
+ * const { variables, control } = useCollectionVariables({ params: { pageSize: 20 } });
15
12
  *
16
- * <CollectionVariablesProvider value={collection}>
13
+ * <CollectionControlProvider value={control}>
17
14
  * <FilterPanel />
18
15
  * <DataTable.Root>...</DataTable.Root>
19
16
  * <Pagination />
20
- * </CollectionVariablesProvider>
17
+ * </CollectionControlProvider>
21
18
  * ```
22
19
  */
23
- export function CollectionVariablesProvider({
20
+ export function CollectionControlProvider({
24
21
  value,
25
22
  children,
26
23
  }: {
27
- value: UseCollectionReturn<string, unknown>;
24
+ value: CollectionControl;
28
25
  children: ReactNode;
29
26
  }) {
30
27
  return (
31
- <CollectionVariablesContext.Provider value={value}>
28
+ <CollectionControlContext.Provider value={value}>
32
29
  {children}
33
- </CollectionVariablesContext.Provider>
30
+ </CollectionControlContext.Provider>
34
31
  );
35
32
  }
36
33
 
37
34
  /**
38
- * Hook to access collection state from the nearest `CollectionVariablesProvider`.
35
+ * Hook to access collection control from the nearest `CollectionControlProvider`.
39
36
  *
40
- * Returns the same interface as `useCollectionVariables()`. Pass a `TFieldName`
37
+ * Returns the `CollectionControl` interface. Pass a `TFieldName`
41
38
  * type parameter to narrow method arguments like `addFilter` / `setSort`.
42
39
  *
43
40
  * @typeParam TFieldName - Union of allowed field name strings (default: `string`).
44
41
  *
45
- * @throws Error if used outside of `CollectionVariablesProvider`.
42
+ * @throws Error if used outside of `CollectionControlProvider`.
46
43
  *
47
44
  * @example
48
45
  * ```tsx
49
46
  * function StatusFilter() {
50
- * const { filters, addFilter, removeFilter } = useCollectionVariablesContext();
47
+ * const { filters, addFilter, removeFilter } = useCollectionControl();
51
48
  * // ...
52
49
  * }
53
50
  *
54
51
  * // With typed field names:
55
52
  * type TaskField = FieldName<typeof tableMetadata, "task">;
56
- * const { addFilter } = useCollectionVariablesContext<TaskField>();
53
+ * const { addFilter } = useCollectionControl<TaskField>();
57
54
  * ```
58
55
  */
59
- export function useCollectionVariablesContext<
56
+ export function useCollectionControl<
60
57
  TFieldName extends string = string,
61
- >(): UseCollectionReturn<TFieldName> {
62
- const ctx = useContext(CollectionVariablesContext);
58
+ >(): CollectionControl<TFieldName> {
59
+ const ctx = useContext(CollectionControlContext);
63
60
  if (!ctx) {
64
61
  throw new Error(
65
- "useCollectionVariablesContext must be used within <CollectionVariablesProvider>",
62
+ "useCollectionControl must be used within <CollectionControlProvider>",
66
63
  );
67
64
  }
68
- return ctx as UseCollectionReturn<TFieldName>;
65
+ return ctx as CollectionControl<TFieldName>;
69
66
  }
@@ -13,10 +13,10 @@ describe("useCollectionVariables", () => {
13
13
  expect(result.current.variables.pagination).toEqual({ first: 20 });
14
14
  expect(result.current.variables.query).toBeUndefined();
15
15
  expect(result.current.variables.order).toBeUndefined();
16
- expect(result.current.filters).toEqual([]);
17
- expect(result.current.sortStates).toEqual([]);
18
- expect(result.current.cursor).toBeNull();
19
- expect(result.current.hasPrevPage).toBe(false);
16
+ expect(result.current.control.filters).toEqual([]);
17
+ expect(result.current.control.sortStates).toEqual([]);
18
+ expect(result.current.control.cursor).toBeNull();
19
+ expect(result.current.control.hasPrevPage).toBe(false);
20
20
  });
21
21
 
22
22
  it("uses custom pageSize", () => {
@@ -34,7 +34,7 @@ describe("useCollectionVariables", () => {
34
34
  },
35
35
  }),
36
36
  );
37
- expect(result.current.sortStates).toEqual([
37
+ expect(result.current.control.sortStates).toEqual([
38
38
  { field: "createdAt", direction: "Desc" },
39
39
  ]);
40
40
  expect(result.current.variables.order).toEqual([
@@ -56,7 +56,7 @@ describe("useCollectionVariables", () => {
56
56
  },
57
57
  }),
58
58
  );
59
- expect(result.current.filters).toHaveLength(1);
59
+ expect(result.current.control.filters).toHaveLength(1);
60
60
  expect(result.current.variables.query).toEqual({
61
61
  status: { eq: "ACTIVE" },
62
62
  });
@@ -71,11 +71,11 @@ describe("useCollectionVariables", () => {
71
71
  const { result } = renderHook(() => useCollectionVariables({}));
72
72
 
73
73
  act(() => {
74
- result.current.addFilter("status", "eq", "ACTIVE");
74
+ result.current.control.addFilter("status", "eq", "ACTIVE");
75
75
  });
76
76
 
77
- expect(result.current.filters).toHaveLength(1);
78
- expect(result.current.filters[0]).toMatchObject({
77
+ expect(result.current.control.filters).toHaveLength(1);
78
+ expect(result.current.control.filters[0]).toMatchObject({
79
79
  field: "status",
80
80
  operator: "eq",
81
81
  value: "ACTIVE",
@@ -89,21 +89,21 @@ describe("useCollectionVariables", () => {
89
89
  const { result } = renderHook(() => useCollectionVariables({}));
90
90
 
91
91
  act(() => {
92
- result.current.addFilter("status", "eq", "ACTIVE");
92
+ result.current.control.addFilter("status", "eq", "ACTIVE");
93
93
  });
94
94
  act(() => {
95
- result.current.addFilter("status", "eq", "INACTIVE");
95
+ result.current.control.addFilter("status", "eq", "INACTIVE");
96
96
  });
97
97
 
98
- expect(result.current.filters).toHaveLength(1);
99
- expect(result.current.filters[0].value).toBe("INACTIVE");
98
+ expect(result.current.control.filters).toHaveLength(1);
99
+ expect(result.current.control.filters[0].value).toBe("INACTIVE");
100
100
  });
101
101
 
102
102
  it("sets filters in bulk", () => {
103
103
  const { result } = renderHook(() => useCollectionVariables({}));
104
104
 
105
105
  act(() => {
106
- result.current.setFilters([
106
+ result.current.control.setFilters([
107
107
  {
108
108
  field: "status",
109
109
  operator: "eq",
@@ -117,7 +117,7 @@ describe("useCollectionVariables", () => {
117
117
  ]);
118
118
  });
119
119
 
120
- expect(result.current.filters).toHaveLength(2);
120
+ expect(result.current.control.filters).toHaveLength(2);
121
121
  expect(result.current.variables.query).toEqual({
122
122
  status: { eq: "ACTIVE" },
123
123
  amount: { gte: 1000 },
@@ -128,29 +128,29 @@ describe("useCollectionVariables", () => {
128
128
  const { result } = renderHook(() => useCollectionVariables({}));
129
129
 
130
130
  act(() => {
131
- result.current.addFilter("status", "eq", "ACTIVE");
132
- result.current.addFilter("amount", "gte", 1000);
131
+ result.current.control.addFilter("status", "eq", "ACTIVE");
132
+ result.current.control.addFilter("amount", "gte", 1000);
133
133
  });
134
134
  act(() => {
135
- result.current.removeFilter("status");
135
+ result.current.control.removeFilter("status");
136
136
  });
137
137
 
138
- expect(result.current.filters).toHaveLength(1);
139
- expect(result.current.filters[0].field).toBe("amount");
138
+ expect(result.current.control.filters).toHaveLength(1);
139
+ expect(result.current.control.filters[0].field).toBe("amount");
140
140
  });
141
141
 
142
142
  it("clears all filters", () => {
143
143
  const { result } = renderHook(() => useCollectionVariables({}));
144
144
 
145
145
  act(() => {
146
- result.current.addFilter("status", "eq", "ACTIVE");
147
- result.current.addFilter("amount", "gte", 1000);
146
+ result.current.control.addFilter("status", "eq", "ACTIVE");
147
+ result.current.control.addFilter("amount", "gte", 1000);
148
148
  });
149
149
  act(() => {
150
- result.current.clearFilters();
150
+ result.current.control.clearFilters();
151
151
  });
152
152
 
153
- expect(result.current.filters).toHaveLength(0);
153
+ expect(result.current.control.filters).toHaveLength(0);
154
154
  expect(result.current.variables.query).toBeUndefined();
155
155
  });
156
156
 
@@ -159,16 +159,16 @@ describe("useCollectionVariables", () => {
159
159
 
160
160
  // Navigate to next page
161
161
  act(() => {
162
- result.current.nextPage("cursor1");
162
+ result.current.control.nextPage("cursor1");
163
163
  });
164
- expect(result.current.cursor).toBe("cursor1");
164
+ expect(result.current.control.cursor).toBe("cursor1");
165
165
 
166
166
  // Adding filter should reset pagination
167
167
  act(() => {
168
- result.current.addFilter("status", "eq", "ACTIVE");
168
+ result.current.control.addFilter("status", "eq", "ACTIVE");
169
169
  });
170
- expect(result.current.cursor).toBeNull();
171
- expect(result.current.hasPrevPage).toBe(false);
170
+ expect(result.current.control.cursor).toBeNull();
171
+ expect(result.current.control.hasPrevPage).toBe(false);
172
172
  });
173
173
  });
174
174
 
@@ -180,10 +180,10 @@ describe("useCollectionVariables", () => {
180
180
  const { result } = renderHook(() => useCollectionVariables({}));
181
181
 
182
182
  act(() => {
183
- result.current.setSort("createdAt", "Desc");
183
+ result.current.control.setSort("createdAt", "Desc");
184
184
  });
185
185
 
186
- expect(result.current.sortStates).toEqual([
186
+ expect(result.current.control.sortStates).toEqual([
187
187
  { field: "createdAt", direction: "Desc" },
188
188
  ]);
189
189
  expect(result.current.variables.order).toEqual([
@@ -195,13 +195,13 @@ describe("useCollectionVariables", () => {
195
195
  const { result } = renderHook(() => useCollectionVariables({}));
196
196
 
197
197
  act(() => {
198
- result.current.setSort("createdAt", "Desc");
198
+ result.current.control.setSort("createdAt", "Desc");
199
199
  });
200
200
  act(() => {
201
- result.current.setSort("name", "Asc");
201
+ result.current.control.setSort("name", "Asc");
202
202
  });
203
203
 
204
- expect(result.current.sortStates).toEqual([
204
+ expect(result.current.control.sortStates).toEqual([
205
205
  { field: "createdAt", direction: "Desc" },
206
206
  { field: "name", direction: "Asc" },
207
207
  ]);
@@ -211,16 +211,16 @@ describe("useCollectionVariables", () => {
211
211
  const { result } = renderHook(() => useCollectionVariables({}));
212
212
 
213
213
  act(() => {
214
- result.current.setSort("createdAt", "Desc");
214
+ result.current.control.setSort("createdAt", "Desc");
215
215
  });
216
216
  act(() => {
217
- result.current.setSort("name", "Asc");
217
+ result.current.control.setSort("name", "Asc");
218
218
  });
219
219
  act(() => {
220
- result.current.setSort("createdAt", "Asc");
220
+ result.current.control.setSort("createdAt", "Asc");
221
221
  });
222
222
 
223
- expect(result.current.sortStates).toEqual([
223
+ expect(result.current.control.sortStates).toEqual([
224
224
  { field: "name", direction: "Asc" },
225
225
  { field: "createdAt", direction: "Asc" },
226
226
  ]);
@@ -230,16 +230,16 @@ describe("useCollectionVariables", () => {
230
230
  const { result } = renderHook(() => useCollectionVariables({}));
231
231
 
232
232
  act(() => {
233
- result.current.setSort("createdAt", "Desc");
233
+ result.current.control.setSort("createdAt", "Desc");
234
234
  });
235
235
  act(() => {
236
- result.current.setSort("name", "Asc");
236
+ result.current.control.setSort("name", "Asc");
237
237
  });
238
238
  act(() => {
239
- result.current.setSort("createdAt");
239
+ result.current.control.setSort("createdAt");
240
240
  });
241
241
 
242
- expect(result.current.sortStates).toEqual([
242
+ expect(result.current.control.sortStates).toEqual([
243
243
  { field: "name", direction: "Asc" },
244
244
  ]);
245
245
  });
@@ -248,13 +248,13 @@ describe("useCollectionVariables", () => {
248
248
  const { result } = renderHook(() => useCollectionVariables({}));
249
249
 
250
250
  act(() => {
251
- result.current.setSort("createdAt", "Desc");
251
+ result.current.control.setSort("createdAt", "Desc");
252
252
  });
253
253
  act(() => {
254
- result.current.clearSort();
254
+ result.current.control.clearSort();
255
255
  });
256
256
 
257
- expect(result.current.sortStates).toEqual([]);
257
+ expect(result.current.control.sortStates).toEqual([]);
258
258
  expect(result.current.variables.order).toBeUndefined();
259
259
  });
260
260
  });
@@ -267,11 +267,11 @@ describe("useCollectionVariables", () => {
267
267
  const { result } = renderHook(() => useCollectionVariables({}));
268
268
 
269
269
  act(() => {
270
- result.current.nextPage("cursor1");
270
+ result.current.control.nextPage("cursor1");
271
271
  });
272
272
 
273
- expect(result.current.cursor).toBe("cursor1");
274
- expect(result.current.paginationDirection).toBe("forward");
273
+ expect(result.current.control.cursor).toBe("cursor1");
274
+ expect(result.current.control.paginationDirection).toBe("forward");
275
275
  expect(result.current.variables.pagination.after).toBe("cursor1");
276
276
  expect(result.current.variables.pagination.first).toBe(20);
277
277
  expect(result.current.variables.pagination.last).toBeUndefined();
@@ -282,11 +282,11 @@ describe("useCollectionVariables", () => {
282
282
  const { result } = renderHook(() => useCollectionVariables({}));
283
283
 
284
284
  act(() => {
285
- result.current.prevPage("cursor1");
285
+ result.current.control.prevPage("cursor1");
286
286
  });
287
287
 
288
- expect(result.current.cursor).toBe("cursor1");
289
- expect(result.current.paginationDirection).toBe("backward");
288
+ expect(result.current.control.cursor).toBe("cursor1");
289
+ expect(result.current.control.paginationDirection).toBe("backward");
290
290
  expect(result.current.variables.pagination.before).toBe("cursor1");
291
291
  expect(result.current.variables.pagination.last).toBe(20);
292
292
  expect(result.current.variables.pagination.first).toBeUndefined();
@@ -297,14 +297,14 @@ describe("useCollectionVariables", () => {
297
297
  const { result } = renderHook(() => useCollectionVariables({}));
298
298
 
299
299
  act(() => {
300
- result.current.prevPage("cursorB");
300
+ result.current.control.prevPage("cursorB");
301
301
  });
302
- expect(result.current.paginationDirection).toBe("backward");
302
+ expect(result.current.control.paginationDirection).toBe("backward");
303
303
 
304
304
  act(() => {
305
- result.current.nextPage("cursorA");
305
+ result.current.control.nextPage("cursorA");
306
306
  });
307
- expect(result.current.paginationDirection).toBe("forward");
307
+ expect(result.current.control.paginationDirection).toBe("forward");
308
308
  expect(result.current.variables.pagination.after).toBe("cursorA");
309
309
  expect(result.current.variables.pagination.first).toBe(20);
310
310
  });
@@ -313,33 +313,33 @@ describe("useCollectionVariables", () => {
313
313
  const { result } = renderHook(() => useCollectionVariables({}));
314
314
 
315
315
  act(() => {
316
- result.current.prevPage("cursor1");
316
+ result.current.control.prevPage("cursor1");
317
317
  });
318
318
  act(() => {
319
- result.current.resetPage();
319
+ result.current.control.resetPage();
320
320
  });
321
321
 
322
- expect(result.current.cursor).toBeNull();
323
- expect(result.current.paginationDirection).toBe("forward");
322
+ expect(result.current.control.cursor).toBeNull();
323
+ expect(result.current.control.paginationDirection).toBe("forward");
324
324
  });
325
325
 
326
326
  it("tracks hasPrevPage from currentPage and hasNextPage from setPageInfo", () => {
327
327
  const { result } = renderHook(() => useCollectionVariables({}));
328
328
 
329
329
  // Initially on page 1: no prev, no next
330
- expect(result.current.hasPrevPage).toBe(false);
331
- expect(result.current.hasNextPage).toBe(false);
330
+ expect(result.current.control.hasPrevPage).toBe(false);
331
+ expect(result.current.control.hasNextPage).toBe(false);
332
332
 
333
333
  // After navigating to next page, hasPrevPage becomes true
334
334
  act(() => {
335
- result.current.nextPage("cursor-end");
335
+ result.current.control.nextPage("cursor-end");
336
336
  });
337
337
 
338
- expect(result.current.hasPrevPage).toBe(true);
338
+ expect(result.current.control.hasPrevPage).toBe(true);
339
339
 
340
340
  // hasNextPage comes from setPageInfo when totalPages is null
341
341
  act(() => {
342
- result.current.setPageInfo({
342
+ result.current.control.setPageInfo({
343
343
  hasNextPage: true,
344
344
  endCursor: "end",
345
345
  hasPreviousPage: false,
@@ -347,7 +347,7 @@ describe("useCollectionVariables", () => {
347
347
  });
348
348
  });
349
349
 
350
- expect(result.current.hasNextPage).toBe(true);
350
+ expect(result.current.control.hasNextPage).toBe(true);
351
351
  });
352
352
  });
353
353
 
@@ -373,7 +373,7 @@ describe("useCollectionVariables", () => {
373
373
  );
374
374
 
375
375
  act(() => {
376
- result.current.nextPage("abc123");
376
+ result.current.control.nextPage("abc123");
377
377
  });
378
378
 
379
379
  expect(result.current.variables).toEqual({
@@ -449,7 +449,7 @@ describe("useCollectionVariables", () => {
449
449
  }),
450
450
  );
451
451
 
452
- expect(result.current.sortStates).toEqual([
452
+ expect(result.current.control.sortStates).toEqual([
453
453
  { field: "dueDate", direction: "Desc" },
454
454
  ]);
455
455
  });
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react";
2
2
  import type { TableMetadata } from "../../generator/metadata-generator";
3
3
  import type {
4
4
  BuildQueryVariables,
5
+ CollectionControl,
5
6
  CollectionVariables,
6
7
  Filter,
7
8
  FilterOperator,
@@ -298,30 +299,32 @@ export function useCollectionVariables(
298
299
  // ---------------------------------------------------------------------------
299
300
  return {
300
301
  variables,
301
- filters,
302
- // Cast needed: implementation accepts FilterOperator (widest),
303
- // but overload signatures narrow via OperatorForField<TFilter, F>.
304
- addFilter: addFilter as UseCollectionReturn["addFilter"],
305
- setFilters,
306
- removeFilter,
307
- clearFilters,
308
- sortStates,
309
- setSort,
310
- clearSort,
311
- pageSize,
312
- setPageSize,
313
- cursor,
314
- paginationDirection,
315
- nextPage,
316
- prevPage,
317
- resetPage,
318
- hasPrevPage,
319
- hasNextPage,
320
- setPageInfo,
321
- currentPage,
322
- totalPages,
323
- goToFirstPage,
324
- goToLastPage,
325
- setTotal,
302
+ control: {
303
+ filters,
304
+ // Cast needed: implementation accepts FilterOperator (widest),
305
+ // but overload signatures narrow via OperatorForField<TFilter, F>.
306
+ addFilter: addFilter as CollectionControl["addFilter"],
307
+ setFilters,
308
+ removeFilter,
309
+ clearFilters,
310
+ sortStates,
311
+ setSort,
312
+ clearSort,
313
+ pageSize,
314
+ setPageSize,
315
+ cursor,
316
+ paginationDirection,
317
+ nextPage,
318
+ prevPage,
319
+ resetPage,
320
+ hasPrevPage,
321
+ hasNextPage,
322
+ setPageInfo,
323
+ currentPage,
324
+ totalPages,
325
+ goToFirstPage,
326
+ goToLastPage,
327
+ setTotal,
328
+ },
326
329
  };
327
330
  }
@@ -8,7 +8,7 @@ import {
8
8
  type ReactNode,
9
9
  } from "react";
10
10
  import { cn } from "../lib/utils";
11
- import { CollectionVariablesProvider } from "../collection/collection-provider";
11
+ import { CollectionControlProvider } from "../collection/collection-provider";
12
12
  import { Table } from "../table";
13
13
  import type { RowAction, SortConfig, UseDataTableReturn } from "../types";
14
14
  import {
@@ -102,7 +102,7 @@ function DataTableProviderComponent<TRow extends Record<string, unknown>>({
102
102
  locale: value.locale,
103
103
  };
104
104
 
105
- const collectionValue = value.collection ?? null;
105
+ const controlValue = value.control ?? null;
106
106
 
107
107
  const inner = (
108
108
  <DataTableContext.Provider value={dataTableValue}>
@@ -110,12 +110,12 @@ function DataTableProviderComponent<TRow extends Record<string, unknown>>({
110
110
  </DataTableContext.Provider>
111
111
  );
112
112
 
113
- // Wrap with CollectionVariablesContext when collection is available
114
- if (collectionValue) {
113
+ // Wrap with CollectionControlProvider when control is available
114
+ if (controlValue) {
115
115
  return (
116
- <CollectionVariablesProvider value={collectionValue}>
116
+ <CollectionControlProvider value={controlValue}>
117
117
  {inner}
118
- </CollectionVariablesProvider>
118
+ </CollectionControlProvider>
119
119
  );
120
120
  }
121
121
 
@@ -1,5 +1,5 @@
1
1
  import { useDataTableContext } from "./data-table-context";
2
- import { useCollectionVariablesContext } from "../collection/collection-provider";
2
+ import { useCollectionControl } from "../collection/collection-provider";
3
3
  import { getLabels } from "./i18n";
4
4
 
5
5
  // =============================================================================
@@ -139,7 +139,7 @@ export function Pagination({ labels, pageSizeOptions }: PaginationProps = {}) {
139
139
  goToLastPage,
140
140
  pageSize,
141
141
  setPageSize,
142
- } = useCollectionVariablesContext();
142
+ } = useCollectionControl();
143
143
 
144
144
  const pl = getLabels(locale).pagination;
145
145
  const firstLabel = labels?.first ?? pl.first;
@@ -14,7 +14,7 @@ import type {
14
14
  } from "../types";
15
15
  import { OPERATORS_BY_FILTER_TYPE } from "../types";
16
16
  import { useDataTableContext } from "./data-table-context";
17
- import { useCollectionVariablesContext } from "../collection/collection-provider";
17
+ import { useCollectionControl } from "../collection/collection-provider";
18
18
  import { getLabels } from "./i18n";
19
19
 
20
20
  /**
@@ -46,7 +46,7 @@ export function SearchFilterForm({
46
46
  } = {}) {
47
47
  const { columns, locale } = useDataTableContext();
48
48
  const { filters, addFilter, removeFilter, clearFilters } =
49
- useCollectionVariablesContext();
49
+ useCollectionControl();
50
50
  const sf = getLabels(locale).searchFilter;
51
51
  const filterableColumns = columns.filter((col) => !!col.filter);
52
52
 
@@ -234,7 +234,7 @@ describe("useDataTable", () => {
234
234
  // Sort state
235
235
  // ---------------------------------------------------------------------------
236
236
  describe("sort state", () => {
237
- it("sortStates is empty when no collection is provided", () => {
237
+ it("sortStates is empty when no control is provided", () => {
238
238
  const { result } = renderHook(() =>
239
239
  useDataTable<TestRow>({ columns: testColumns, data: testData }),
240
240
  );
@@ -242,7 +242,7 @@ describe("useDataTable", () => {
242
242
  expect(result.current.sortStates).toEqual([]);
243
243
  });
244
244
 
245
- it("onSort is undefined when no collection is provided", () => {
245
+ it("onSort is undefined when no control is provided", () => {
246
246
  const { result } = renderHook(() =>
247
247
  useDataTable<TestRow>({ columns: testColumns, data: testData }),
248
248
  );
@@ -13,7 +13,7 @@ import type {
13
13
  *
14
14
  * @example
15
15
  * ```tsx
16
- * const { variables, ...collection } = useCollectionVariables({ params: { pageSize: 20 } });
16
+ * const { variables, control } = useCollectionVariables({ params: { pageSize: 20 } });
17
17
  * const { query, order, pagination } = variables;
18
18
  * const [result] = useQuery({
19
19
  * query: GET_ORDERS,
@@ -24,7 +24,7 @@ import type {
24
24
  * columns,
25
25
  * data: result.data?.orders,
26
26
  * loading: result.fetching,
27
- * collection,
27
+ * control,
28
28
  * });
29
29
  *
30
30
  * <DataTable.Provider value={table}>
@@ -43,7 +43,7 @@ export function useDataTable<TRow extends Record<string, unknown>>(
43
43
  data,
44
44
  loading = false,
45
45
  error = null,
46
- collection,
46
+ control,
47
47
  onClickRow,
48
48
  rowActions,
49
49
  locale = "en",
@@ -76,20 +76,15 @@ export function useDataTable<TRow extends Record<string, unknown>>(
76
76
  );
77
77
  }, [data]);
78
78
 
79
- // Sync pageInfo to collection so hasPrevPage/hasNextPage are up-to-date
79
+ // Sync pageInfo to control so hasPrevPage/hasNextPage are up-to-date
80
80
  useEffect(() => {
81
81
  if (data?.pageInfo) {
82
- collection?.setPageInfo(data.pageInfo);
82
+ control?.setPageInfo(data.pageInfo);
83
83
  }
84
84
  if (data?.total != null) {
85
- collection?.setTotal(data.total);
85
+ control?.setTotal(data.total);
86
86
  }
87
- }, [
88
- data?.pageInfo,
89
- data?.total,
90
- collection?.setPageInfo,
91
- collection?.setTotal,
92
- ]);
87
+ }, [data?.pageInfo, data?.total, control?.setPageInfo, control?.setTotal]);
93
88
 
94
89
  // ---------------------------------------------------------------------------
95
90
  // Column visibility management
@@ -131,24 +126,24 @@ export function useDataTable<TRow extends Record<string, unknown>>(
131
126
  );
132
127
 
133
128
  // ---------------------------------------------------------------------------
134
- // Pagination (delegated from collection)
129
+ // Pagination (delegated from control)
135
130
  // ---------------------------------------------------------------------------
136
131
  const nextPage = useCallback(
137
132
  (endCursor: string) => {
138
- collection?.nextPage(endCursor);
133
+ control?.nextPage(endCursor);
139
134
  },
140
- [collection],
135
+ [control],
141
136
  );
142
137
 
143
138
  const prevPage = useCallback(
144
139
  (startCursor: string) => {
145
- collection?.prevPage(startCursor);
140
+ control?.prevPage(startCursor);
146
141
  },
147
- [collection],
142
+ [control],
148
143
  );
149
144
 
150
- const hasPrevPage = collection?.hasPrevPage ?? false;
151
- const hasNextPage = collection?.hasNextPage ?? false;
145
+ const hasPrevPage = control?.hasPrevPage ?? false;
146
+ const hasNextPage = control?.hasNextPage ?? false;
152
147
 
153
148
  // ---------------------------------------------------------------------------
154
149
  // Row Operations (Optimistic Updates)
@@ -209,19 +204,19 @@ export function useDataTable<TRow extends Record<string, unknown>>(
209
204
  );
210
205
 
211
206
  // ---------------------------------------------------------------------------
212
- // Sort (delegated from collection)
207
+ // Sort (delegated from control)
213
208
  // ---------------------------------------------------------------------------
214
209
  const sortStates = useMemo<SortState[]>(() => {
215
- return collection?.sortStates ?? [];
216
- }, [collection?.sortStates]);
210
+ return control?.sortStates ?? [];
211
+ }, [control?.sortStates]);
217
212
 
218
213
  const onSort = useMemo<
219
214
  ((field: string, direction?: "Asc" | "Desc") => void) | undefined
220
215
  >(() => {
221
- if (!collection) return undefined;
216
+ if (!control) return undefined;
222
217
  return (field: string, direction?: "Asc" | "Desc") =>
223
- collection.setSort(field, direction);
224
- }, [collection]);
218
+ control.setSort(field, direction);
219
+ }, [control]);
225
220
 
226
221
  // ---------------------------------------------------------------------------
227
222
  // Return
@@ -254,8 +249,8 @@ export function useDataTable<TRow extends Record<string, unknown>>(
254
249
  deleteRow,
255
250
  insertRow,
256
251
 
257
- // Collection (passthrough for DataTable.Provider)
258
- collection,
252
+ // Control (passthrough for DataTable.Provider)
253
+ control,
259
254
 
260
255
  // Row interaction (passthrough for DataTable.Provider)
261
256
  onClickRow,
@@ -18,6 +18,7 @@ export type {
18
18
  ColumnDefinition,
19
19
  UseCollectionOptions,
20
20
  UseCollectionReturn,
21
+ CollectionControl,
21
22
  UseDataTableOptions,
22
23
  UseDataTableReturn,
23
24
  RowAction,
@@ -44,8 +45,8 @@ export {
44
45
  // Collection
45
46
  export { useCollectionVariables } from "./collection/use-collection";
46
47
  export {
47
- CollectionVariablesProvider,
48
- useCollectionVariablesContext,
48
+ CollectionControlProvider,
49
+ useCollectionControl,
49
50
  } from "./collection/collection-provider";
50
51
 
51
52
  // Table (static)
@@ -466,40 +466,25 @@ export interface UseCollectionOptions<
466
466
  }
467
467
 
468
468
  /**
469
- * Return type of `useCollectionVariables` hook.
469
+ * Collection control interface for UI components to interact with
470
+ * filter, sort, and pagination state.
470
471
  *
471
472
  * Methods that accept a field name are typed with `TFieldName` so that
472
473
  * auto-completion works when a concrete union is supplied.
473
474
  *
474
475
  * **Note:** Methods that accept `TFieldName` use *method syntax* intentionally
475
- * so that `UseCollectionReturn<"a" | "b">` remains assignable to
476
- * `UseCollectionReturn<string>` (bivariant method check).
476
+ * so that `CollectionControl<"a" | "b">` remains assignable to
477
+ * `CollectionControl<string>` (bivariant method check).
477
478
  * Methods that don't depend on `TFieldName` use property syntax so they
478
479
  * can be safely destructured without triggering `unbound-method` lint rules.
479
480
  *
480
481
  * @typeParam TFieldName - Union of allowed field name strings (default: `string`).
481
- * @typeParam TVariables - Type of `variables` property.
482
+ * @typeParam TFilter - Filter type (default: `Filter<TFieldName>`).
482
483
  */
483
- export interface UseCollectionReturn<
484
+ export interface CollectionControl<
484
485
  TFieldName extends string = string,
485
- TVariables = CollectionVariables,
486
486
  TFilter = Filter<TFieldName>,
487
487
  > {
488
- /**
489
- * Collection variables split into explicit sub-properties
490
- * for direct mapping to GraphQL query variables.
491
- *
492
- * @example
493
- * ```tsx
494
- * const { query, order, pagination } = collection.variables;
495
- * const [result] = useQuery({
496
- * query: GET_TASKS,
497
- * variables: { ...pagination, query, order },
498
- * });
499
- * ```
500
- */
501
- variables: TVariables;
502
-
503
488
  // Filter operations
504
489
  /** Current active filters */
505
490
  filters: Filter[];
@@ -557,6 +542,49 @@ export interface UseCollectionReturn<
557
542
  setTotal: (total: number) => void;
558
543
  }
559
544
 
545
+ /**
546
+ * Return type of `useCollectionVariables` hook.
547
+ *
548
+ * @typeParam TFieldName - Union of allowed field name strings (default: `string`).
549
+ * @typeParam TVariables - Type of `variables` property.
550
+ * @typeParam TFilter - Filter type (default: `Filter<TFieldName>`).
551
+ */
552
+ export interface UseCollectionReturn<
553
+ TFieldName extends string = string,
554
+ TVariables = CollectionVariables,
555
+ TFilter = Filter<TFieldName>,
556
+ > {
557
+ /**
558
+ * Collection variables split into explicit sub-properties
559
+ * for direct mapping to GraphQL query variables.
560
+ *
561
+ * @example
562
+ * ```tsx
563
+ * const { variables, control } = useCollectionVariables({ params: { pageSize: 20 } });
564
+ * const { query, order, pagination } = variables;
565
+ * const [result] = useQuery({
566
+ * query: GET_TASKS,
567
+ * variables: { ...pagination, query, order },
568
+ * });
569
+ * ```
570
+ */
571
+ variables: TVariables;
572
+
573
+ /**
574
+ * Collection control interface for UI components.
575
+ *
576
+ * Pass this to `useDataTable({ control })` to connect filter, sort,
577
+ * and pagination state to the data table.
578
+ *
579
+ * @example
580
+ * ```tsx
581
+ * const { variables, control } = useCollectionVariables({ params: { pageSize: 20 } });
582
+ * const table = useDataTable({ columns, data: result.data?.orders, control });
583
+ * ```
584
+ */
585
+ control: CollectionControl<TFieldName, TFilter>;
586
+ }
587
+
560
588
  // =============================================================================
561
589
  // useDataTable Types
562
590
  // =============================================================================
@@ -573,8 +601,8 @@ export interface UseDataTableOptions<TRow extends Record<string, unknown>> {
573
601
  loading?: boolean;
574
602
  /** Error */
575
603
  error?: Error | null;
576
- /** Collection state for sort/pagination integration */
577
- collection?: UseCollectionReturn<string, unknown>;
604
+ /** Collection control for sort/filter/pagination integration */
605
+ control?: CollectionControl;
578
606
  /** Handler called when a row is clicked */
579
607
  onClickRow?: (row: TRow) => void;
580
608
  /** Row action definitions for the actions column */
@@ -680,9 +708,9 @@ export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
680
708
  /** Optimistically insert a row */
681
709
  insertRow: (row: TRow) => { rollback: () => void };
682
710
 
683
- // Collection (passthrough for DataTable.Provider)
684
- /** Collection state passed through from options */
685
- collection: UseCollectionReturn<string, unknown> | undefined;
711
+ // Control (passthrough for DataTable.Provider)
712
+ /** Collection control passed through from options */
713
+ control: CollectionControl | undefined;
686
714
 
687
715
  // Row interaction (passthrough for DataTable.Provider)
688
716
  /** Handler called when a row is clicked */
@@ -1,9 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { vi } from "vitest";
3
- import type { Column, UseCollectionReturn } from "../component/types";
3
+ import type { Column, CollectionControl } from "../component/types";
4
4
  import { DataTableContext } from "../component/data-table/data-table-context";
5
5
  import type { DataTableContextValue } from "../component/data-table/data-table-context";
6
- import { CollectionVariablesProvider } from "../component/collection/collection-provider";
6
+ import { CollectionControlProvider } from "../component/collection/collection-provider";
7
7
 
8
8
  // =============================================================================
9
9
  // Mock factory: DataTableContext
@@ -47,14 +47,9 @@ export function createMockDataTableContext<T extends Record<string, unknown>>(
47
47
  // =============================================================================
48
48
 
49
49
  export function createMockCollectionContext(
50
- overrides?: Partial<UseCollectionReturn<string, unknown>>,
51
- ): UseCollectionReturn<string, unknown> {
50
+ overrides?: Partial<CollectionControl>,
51
+ ): CollectionControl {
52
52
  return {
53
- variables: {
54
- query: undefined,
55
- order: undefined,
56
- pagination: { first: 20 },
57
- },
58
53
  filters: [],
59
54
  addFilter: vi.fn(),
60
55
  setFilters: vi.fn(),
@@ -110,7 +105,7 @@ export function createTestProviders<
110
105
  columns: Column<T>[];
111
106
  rows: T[];
112
107
  dataTableDefaults?: Partial<DataTableContextValue<T>>;
113
- collectionDefaults?: Partial<UseCollectionReturn<string, unknown>>;
108
+ collectionDefaults?: Partial<CollectionControl>;
114
109
  }) {
115
110
  return function TestProviders({
116
111
  children,
@@ -119,10 +114,10 @@ export function createTestProviders<
119
114
  }: {
120
115
  children: ReactNode;
121
116
  dataTable?: Partial<DataTableContextValue<T>>;
122
- collection?: Partial<UseCollectionReturn<string, unknown>>;
117
+ collection?: Partial<CollectionControl>;
123
118
  }) {
124
119
  return (
125
- <CollectionVariablesProvider
120
+ <CollectionControlProvider
126
121
  value={createMockCollectionContext({
127
122
  ...defaults.collectionDefaults,
128
123
  ...collection,
@@ -136,7 +131,7 @@ export function createTestProviders<
136
131
  >
137
132
  {children}
138
133
  </DataTableContext.Provider>
139
- </CollectionVariablesProvider>
134
+ </CollectionControlProvider>
140
135
  );
141
136
  };
142
137
  }