@tanstack/react-table 9.0.0-alpha.9 → 9.0.0-beta.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.
Files changed (82) hide show
  1. package/README.md +127 -0
  2. package/dist/FlexRender.cjs +61 -0
  3. package/dist/FlexRender.cjs.map +1 -0
  4. package/dist/FlexRender.d.cts +51 -0
  5. package/dist/FlexRender.d.ts +51 -0
  6. package/dist/FlexRender.js +58 -0
  7. package/dist/FlexRender.js.map +1 -0
  8. package/dist/Subscribe.cjs +13 -0
  9. package/dist/Subscribe.cjs.map +1 -0
  10. package/dist/Subscribe.d.cts +101 -0
  11. package/dist/Subscribe.d.ts +101 -0
  12. package/dist/Subscribe.js +13 -0
  13. package/dist/Subscribe.js.map +1 -0
  14. package/dist/_virtual/_rolldown/runtime.cjs +29 -0
  15. package/dist/createTableHook.cjs +313 -0
  16. package/dist/createTableHook.cjs.map +1 -0
  17. package/dist/createTableHook.d.cts +358 -0
  18. package/dist/createTableHook.d.ts +358 -0
  19. package/dist/createTableHook.js +311 -0
  20. package/dist/createTableHook.js.map +1 -0
  21. package/dist/flex-render.cjs +5 -0
  22. package/dist/flex-render.d.cts +2 -0
  23. package/dist/flex-render.d.ts +2 -0
  24. package/dist/flex-render.js +3 -0
  25. package/dist/index.cjs +18 -0
  26. package/dist/index.d.cts +6 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +8 -0
  29. package/dist/legacy.cjs +14 -0
  30. package/dist/legacy.d.cts +2 -0
  31. package/dist/legacy.d.ts +2 -0
  32. package/dist/legacy.js +3 -0
  33. package/dist/reactivity.cjs +34 -0
  34. package/dist/reactivity.cjs.map +1 -0
  35. package/dist/reactivity.js +34 -0
  36. package/dist/reactivity.js.map +1 -0
  37. package/dist/static-functions.cjs +9 -0
  38. package/dist/static-functions.d.cts +1 -0
  39. package/dist/static-functions.d.ts +1 -0
  40. package/dist/static-functions.js +3 -0
  41. package/dist/useLegacyTable.cjs +191 -0
  42. package/dist/useLegacyTable.cjs.map +1 -0
  43. package/dist/useLegacyTable.d.cts +233 -0
  44. package/dist/useLegacyTable.d.ts +233 -0
  45. package/dist/useLegacyTable.js +181 -0
  46. package/dist/useLegacyTable.js.map +1 -0
  47. package/dist/useTable.cjs +72 -0
  48. package/dist/useTable.cjs.map +1 -0
  49. package/dist/useTable.d.cts +122 -0
  50. package/dist/useTable.d.ts +122 -0
  51. package/dist/useTable.js +72 -0
  52. package/dist/useTable.js.map +1 -0
  53. package/package.json +41 -22
  54. package/skills/react/client-to-server/SKILL.md +377 -0
  55. package/skills/react/compose-with-tanstack-form/SKILL.md +363 -0
  56. package/skills/react/compose-with-tanstack-pacer/SKILL.md +287 -0
  57. package/skills/react/compose-with-tanstack-query/SKILL.md +467 -0
  58. package/skills/react/compose-with-tanstack-store/SKILL.md +347 -0
  59. package/skills/react/compose-with-tanstack-virtual/SKILL.md +388 -0
  60. package/skills/react/compose-with-tanstack-virtual/references/column-virtualization-and-infinite-scroll.md +136 -0
  61. package/skills/react/getting-started/SKILL.md +388 -0
  62. package/skills/react/migrate-v8-to-v9/SKILL.md +488 -0
  63. package/skills/react/production-readiness/SKILL.md +341 -0
  64. package/skills/react/react-subscribe-compiler-compat/SKILL.md +269 -0
  65. package/skills/react/table-state/SKILL.md +432 -0
  66. package/src/FlexRender.tsx +136 -0
  67. package/src/Subscribe.ts +153 -0
  68. package/src/createTableHook.tsx +1121 -0
  69. package/src/flex-render.ts +1 -0
  70. package/src/index.ts +6 -0
  71. package/src/legacy.ts +3 -0
  72. package/src/reactivity.ts +41 -0
  73. package/src/static-functions.ts +1 -0
  74. package/src/useLegacyTable.ts +487 -0
  75. package/src/useTable.ts +191 -0
  76. package/dist/cjs/index.cjs +0 -77
  77. package/dist/cjs/index.cjs.map +0 -1
  78. package/dist/cjs/index.d.cts +0 -9
  79. package/dist/esm/index.d.ts +0 -9
  80. package/dist/esm/index.js +0 -55
  81. package/dist/esm/index.js.map +0 -1
  82. package/src/index.tsx +0 -92
@@ -0,0 +1,1121 @@
1
+ 'use client'
2
+ /* eslint-disable @eslint-react/no-context-provider */
3
+ import React, { createContext, useContext, useMemo } from 'react'
4
+ import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'
5
+ import { useTable } from './useTable'
6
+ import { FlexRender } from './FlexRender'
7
+ import type {
8
+ AccessorFn,
9
+ AccessorFnColumnDef,
10
+ AccessorKeyColumnDef,
11
+ Cell,
12
+ CellContext,
13
+ CellData,
14
+ Column,
15
+ ColumnDef,
16
+ DeepKeys,
17
+ DeepValue,
18
+ DisplayColumnDef,
19
+ GroupColumnDef,
20
+ Header,
21
+ IdentifiedColumnDef,
22
+ NoInfer,
23
+ Row,
24
+ RowData,
25
+ Table,
26
+ TableFeatures,
27
+ TableOptions,
28
+ TableState,
29
+ } from '@tanstack/table-core'
30
+ import type { ComponentType, ReactNode } from 'react'
31
+ import type { ReactTable } from './useTable'
32
+
33
+ // =============================================================================
34
+ // Enhanced Context Types with Pre-bound Components
35
+ // =============================================================================
36
+
37
+ /**
38
+ * Enhanced CellContext with pre-bound cell components.
39
+ * The `cell` property includes the registered cellComponents.
40
+ */
41
+ export type AppCellContext<
42
+ TFeatures extends TableFeatures,
43
+ TData extends RowData,
44
+ TValue extends CellData,
45
+ TCellComponents extends Record<string, ComponentType<any>>,
46
+ > = {
47
+ cell: Cell<TFeatures, TData, TValue> &
48
+ TCellComponents & { FlexRender: () => ReactNode }
49
+ column: Column<TFeatures, TData, TValue>
50
+ getValue: CellContext<TFeatures, TData, TValue>['getValue']
51
+ renderValue: CellContext<TFeatures, TData, TValue>['renderValue']
52
+ row: Row<TFeatures, TData>
53
+ table: Table<TFeatures, TData>
54
+ }
55
+
56
+ /**
57
+ * Enhanced HeaderContext with pre-bound header components.
58
+ * The `header` property includes the registered headerComponents.
59
+ */
60
+ export type AppHeaderContext<
61
+ TFeatures extends TableFeatures,
62
+ TData extends RowData,
63
+ TValue extends CellData,
64
+ THeaderComponents extends Record<string, ComponentType<any>>,
65
+ > = {
66
+ column: Column<TFeatures, TData, TValue>
67
+ header: Header<TFeatures, TData, TValue> &
68
+ THeaderComponents & { FlexRender: () => ReactNode }
69
+ table: Table<TFeatures, TData>
70
+ }
71
+
72
+ // =============================================================================
73
+ // Enhanced Column Definition Types
74
+ // =============================================================================
75
+
76
+ /**
77
+ * Template type for column definitions that can be a string or a function.
78
+ */
79
+ export type AppColumnDefTemplate<TProps extends object> =
80
+ | string
81
+ | ((props: TProps) => any)
82
+
83
+ /**
84
+ * Enhanced column definition base with pre-bound components in cell/header/footer contexts.
85
+ */
86
+ export type AppColumnDefBase<
87
+ TFeatures extends TableFeatures,
88
+ TData extends RowData,
89
+ TValue extends CellData,
90
+ TCellComponents extends Record<string, ComponentType<any>>,
91
+ THeaderComponents extends Record<string, ComponentType<any>>,
92
+ > = Omit<
93
+ IdentifiedColumnDef<TFeatures, TData, TValue>,
94
+ 'cell' | 'header' | 'footer'
95
+ > & {
96
+ cell?: AppColumnDefTemplate<
97
+ AppCellContext<TFeatures, TData, TValue, TCellComponents>
98
+ >
99
+ header?: AppColumnDefTemplate<
100
+ AppHeaderContext<TFeatures, TData, TValue, THeaderComponents>
101
+ >
102
+ footer?: AppColumnDefTemplate<
103
+ AppHeaderContext<TFeatures, TData, TValue, THeaderComponents>
104
+ >
105
+ }
106
+
107
+ /**
108
+ * Enhanced display column definition with pre-bound components.
109
+ */
110
+ export type AppDisplayColumnDef<
111
+ TFeatures extends TableFeatures,
112
+ TData extends RowData,
113
+ TCellComponents extends Record<string, ComponentType<any>>,
114
+ THeaderComponents extends Record<string, ComponentType<any>>,
115
+ > = Omit<
116
+ DisplayColumnDef<TFeatures, TData, unknown>,
117
+ 'cell' | 'header' | 'footer'
118
+ > & {
119
+ cell?: AppColumnDefTemplate<
120
+ AppCellContext<TFeatures, TData, unknown, TCellComponents>
121
+ >
122
+ header?: AppColumnDefTemplate<
123
+ AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>
124
+ >
125
+ footer?: AppColumnDefTemplate<
126
+ AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>
127
+ >
128
+ }
129
+
130
+ /**
131
+ * Enhanced group column definition with pre-bound components.
132
+ */
133
+ export type AppGroupColumnDef<
134
+ TFeatures extends TableFeatures,
135
+ TData extends RowData,
136
+ TCellComponents extends Record<string, ComponentType<any>>,
137
+ THeaderComponents extends Record<string, ComponentType<any>>,
138
+ > = Omit<
139
+ GroupColumnDef<TFeatures, TData, unknown>,
140
+ 'cell' | 'header' | 'footer' | 'columns'
141
+ > & {
142
+ cell?: AppColumnDefTemplate<
143
+ AppCellContext<TFeatures, TData, unknown, TCellComponents>
144
+ >
145
+ header?: AppColumnDefTemplate<
146
+ AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>
147
+ >
148
+ footer?: AppColumnDefTemplate<
149
+ AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>
150
+ >
151
+ columns?: Array<ColumnDef<TFeatures, TData, unknown>>
152
+ }
153
+
154
+ // =============================================================================
155
+ // Enhanced Column Helper Type
156
+ // =============================================================================
157
+
158
+ /**
159
+ * Enhanced column helper with pre-bound components in cell/header/footer contexts.
160
+ * This enables TypeScript to know about the registered components when defining columns.
161
+ */
162
+ export type AppColumnHelper<
163
+ TFeatures extends TableFeatures,
164
+ TData extends RowData,
165
+ TCellComponents extends Record<string, ComponentType<any>>,
166
+ THeaderComponents extends Record<string, ComponentType<any>>,
167
+ > = {
168
+ /**
169
+ * Creates a data column definition with an accessor key or function.
170
+ * The cell, header, and footer contexts include pre-bound components.
171
+ */
172
+ accessor: <
173
+ TAccessor extends AccessorFn<TData> | DeepKeys<TData>,
174
+ TValue extends TAccessor extends AccessorFn<TData, infer TReturn>
175
+ ? TReturn
176
+ : TAccessor extends DeepKeys<TData>
177
+ ? DeepValue<TData, TAccessor>
178
+ : never,
179
+ >(
180
+ accessor: TAccessor,
181
+ column: TAccessor extends AccessorFn<TData>
182
+ ? AppColumnDefBase<
183
+ TFeatures,
184
+ TData,
185
+ TValue,
186
+ TCellComponents,
187
+ THeaderComponents
188
+ > & { id: string }
189
+ : AppColumnDefBase<
190
+ TFeatures,
191
+ TData,
192
+ TValue,
193
+ TCellComponents,
194
+ THeaderComponents
195
+ >,
196
+ ) => TAccessor extends AccessorFn<TData>
197
+ ? AccessorFnColumnDef<TFeatures, TData, TValue>
198
+ : AccessorKeyColumnDef<TFeatures, TData, TValue>
199
+
200
+ /**
201
+ * Wraps an array of column definitions to preserve each column's individual TValue type.
202
+ */
203
+ columns: <TColumns extends ReadonlyArray<ColumnDef<TFeatures, TData, any>>>(
204
+ columns: [...TColumns],
205
+ ) => Array<ColumnDef<TFeatures, TData, any>> & [...TColumns]
206
+
207
+ /**
208
+ * Creates a display column definition for non-data columns.
209
+ * The cell, header, and footer contexts include pre-bound components.
210
+ */
211
+ display: (
212
+ column: AppDisplayColumnDef<
213
+ TFeatures,
214
+ TData,
215
+ TCellComponents,
216
+ THeaderComponents
217
+ >,
218
+ ) => DisplayColumnDef<TFeatures, TData, unknown>
219
+
220
+ /**
221
+ * Creates a group column definition with nested child columns.
222
+ * The cell, header, and footer contexts include pre-bound components.
223
+ */
224
+ group: (
225
+ column: AppGroupColumnDef<
226
+ TFeatures,
227
+ TData,
228
+ TCellComponents,
229
+ THeaderComponents
230
+ >,
231
+ ) => GroupColumnDef<TFeatures, TData, unknown>
232
+ }
233
+
234
+ // =============================================================================
235
+ // CreateTableHook Options and Props
236
+ // =============================================================================
237
+
238
+ /**
239
+ * Options for creating a table hook with pre-bound components and default table options.
240
+ * Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'.
241
+ */
242
+ export type CreateTableHookOptions<
243
+ TFeatures extends TableFeatures,
244
+ TTableComponents extends Record<string, ComponentType<any>>,
245
+ TCellComponents extends Record<string, ComponentType<any>>,
246
+ THeaderComponents extends Record<string, ComponentType<any>>,
247
+ > = Omit<
248
+ TableOptions<TFeatures, any>,
249
+ 'columns' | 'data' | 'store' | 'state' | 'initialState'
250
+ > & {
251
+ /**
252
+ * Table-level components that need access to the table instance.
253
+ * These are available directly on the table object returned by useAppTable.
254
+ * Use `useTableContext()` inside these components.
255
+ * @example { PaginationControls, GlobalFilter, RowCount }
256
+ */
257
+ tableComponents?: TTableComponents
258
+ /**
259
+ * Cell-level components that need access to the cell instance.
260
+ * These are available on the cell object passed to AppCell's children.
261
+ * Use `useCellContext()` inside these components.
262
+ * @example { TextCell, NumberCell, DateCell, CurrencyCell }
263
+ */
264
+ cellComponents?: TCellComponents
265
+ /**
266
+ * Header-level components that need access to the header instance.
267
+ * These are available on the header object passed to AppHeader/AppFooter's children.
268
+ * Use `useHeaderContext()` inside these components.
269
+ * @example { SortIndicator, ColumnFilter, ResizeHandle }
270
+ */
271
+ headerComponents?: THeaderComponents
272
+ }
273
+
274
+ /**
275
+ * Props for AppTable component - without selector
276
+ */
277
+ export interface AppTablePropsWithoutSelector {
278
+ children: ReactNode
279
+ selector?: never
280
+ }
281
+
282
+ /**
283
+ * Props for AppTable component - with selector
284
+ */
285
+ export interface AppTablePropsWithSelector<
286
+ TFeatures extends TableFeatures,
287
+ TSelected,
288
+ > {
289
+ children: (state: TSelected) => ReactNode
290
+ selector: (state: TableState<TFeatures>) => TSelected
291
+ }
292
+
293
+ /**
294
+ * Props for AppCell component - without selector
295
+ */
296
+ export interface AppCellPropsWithoutSelector<
297
+ TFeatures extends TableFeatures,
298
+ TData extends RowData,
299
+ TValue extends CellData,
300
+ TCellComponents extends Record<string, ComponentType<any>>,
301
+ > {
302
+ cell: Cell<TFeatures, TData, TValue>
303
+ children: (
304
+ cell: Cell<TFeatures, TData, TValue> &
305
+ TCellComponents & { FlexRender: () => ReactNode },
306
+ ) => ReactNode
307
+ selector?: never
308
+ }
309
+
310
+ /**
311
+ * Props for AppCell component - with selector
312
+ */
313
+ export interface AppCellPropsWithSelector<
314
+ TFeatures extends TableFeatures,
315
+ TData extends RowData,
316
+ TValue extends CellData,
317
+ TCellComponents extends Record<string, ComponentType<any>>,
318
+ TSelected,
319
+ > {
320
+ cell: Cell<TFeatures, TData, TValue>
321
+ children: (
322
+ cell: Cell<TFeatures, TData, TValue> &
323
+ TCellComponents & { FlexRender: () => ReactNode },
324
+ state: TSelected,
325
+ ) => ReactNode
326
+ selector: (state: TableState<TFeatures>) => TSelected
327
+ }
328
+
329
+ /**
330
+ * Props for AppHeader/AppFooter component - without selector
331
+ */
332
+ export interface AppHeaderPropsWithoutSelector<
333
+ TFeatures extends TableFeatures,
334
+ TData extends RowData,
335
+ TValue extends CellData,
336
+ THeaderComponents extends Record<string, ComponentType<any>>,
337
+ > {
338
+ header: Header<TFeatures, TData, TValue>
339
+ children: (
340
+ header: Header<TFeatures, TData, TValue> &
341
+ THeaderComponents & { FlexRender: () => ReactNode },
342
+ ) => ReactNode
343
+ selector?: never
344
+ }
345
+
346
+ /**
347
+ * Props for AppHeader/AppFooter component - with selector
348
+ */
349
+ export interface AppHeaderPropsWithSelector<
350
+ TFeatures extends TableFeatures,
351
+ TData extends RowData,
352
+ TValue extends CellData,
353
+ THeaderComponents extends Record<string, ComponentType<any>>,
354
+ TSelected,
355
+ > {
356
+ header: Header<TFeatures, TData, TValue>
357
+ children: (
358
+ header: Header<TFeatures, TData, TValue> &
359
+ THeaderComponents & { FlexRender: () => ReactNode },
360
+ state: TSelected,
361
+ ) => ReactNode
362
+ selector: (state: TableState<TFeatures>) => TSelected
363
+ }
364
+
365
+ /**
366
+ * Component type for AppCell - wraps a cell and provides cell context with optional Subscribe
367
+ */
368
+ export interface AppCellComponent<
369
+ TFeatures extends TableFeatures,
370
+ TData extends RowData,
371
+ TCellComponents extends Record<string, ComponentType<any>>,
372
+ > {
373
+ <TValue extends CellData = CellData>(
374
+ props: AppCellPropsWithoutSelector<
375
+ TFeatures,
376
+ TData,
377
+ TValue,
378
+ TCellComponents
379
+ >,
380
+ ): ReactNode
381
+ <TValue extends CellData = CellData, TSelected = unknown>(
382
+ props: AppCellPropsWithSelector<
383
+ TFeatures,
384
+ TData,
385
+ TValue,
386
+ TCellComponents,
387
+ TSelected
388
+ >,
389
+ ): ReactNode
390
+ }
391
+
392
+ /**
393
+ * Component type for AppHeader/AppFooter - wraps a header and provides header context with optional Subscribe
394
+ */
395
+ export interface AppHeaderComponent<
396
+ TFeatures extends TableFeatures,
397
+ TData extends RowData,
398
+ THeaderComponents extends Record<string, ComponentType<any>>,
399
+ > {
400
+ <TValue extends CellData = CellData>(
401
+ props: AppHeaderPropsWithoutSelector<
402
+ TFeatures,
403
+ TData,
404
+ TValue,
405
+ THeaderComponents
406
+ >,
407
+ ): ReactNode
408
+ <TValue extends CellData = CellData, TSelected = unknown>(
409
+ props: AppHeaderPropsWithSelector<
410
+ TFeatures,
411
+ TData,
412
+ TValue,
413
+ THeaderComponents,
414
+ TSelected
415
+ >,
416
+ ): ReactNode
417
+ }
418
+
419
+ /**
420
+ * Component type for AppTable - root wrapper with optional Subscribe
421
+ */
422
+ export interface AppTableComponent<TFeatures extends TableFeatures> {
423
+ (props: AppTablePropsWithoutSelector): ReactNode
424
+ <TSelected>(props: AppTablePropsWithSelector<TFeatures, TSelected>): ReactNode
425
+ }
426
+
427
+ /**
428
+ * Extended table API returned by useAppTable with all App wrapper components
429
+ */
430
+ export type AppReactTable<
431
+ TFeatures extends TableFeatures,
432
+ TData extends RowData,
433
+ TSelected,
434
+ TTableComponents extends Record<string, ComponentType<any>>,
435
+ TCellComponents extends Record<string, ComponentType<any>>,
436
+ THeaderComponents extends Record<string, ComponentType<any>>,
437
+ > = ReactTable<TFeatures, TData, TSelected> &
438
+ NoInfer<TTableComponents> & {
439
+ /**
440
+ * Root wrapper component that provides table context with optional Subscribe.
441
+ * @example
442
+ * ```tsx
443
+ * // Without selector - children is ReactNode
444
+ * <table.AppTable>
445
+ * <table>...</table>
446
+ * </table.AppTable>
447
+ *
448
+ * // With selector - children receives selected state
449
+ * <table.AppTable selector={(s) => s.pagination}>
450
+ * {(pagination) => <div>Page {pagination.pageIndex}</div>}
451
+ * </table.AppTable>
452
+ * ```
453
+ */
454
+ AppTable: AppTableComponent<TFeatures>
455
+ /**
456
+ * Wraps a cell and provides cell context with pre-bound cellComponents.
457
+ * Optionally accepts a selector for Subscribe functionality.
458
+ * @example
459
+ * ```tsx
460
+ * // Without selector
461
+ * <table.AppCell cell={cell}>
462
+ * {(c) => <td><c.TextCell /></td>}
463
+ * </table.AppCell>
464
+ *
465
+ * // With selector - children receives cell and selected state
466
+ * <table.AppCell cell={cell} selector={(s) => s.columnFilters}>
467
+ * {(c, filters) => <td>{filters.length}</td>}
468
+ * </table.AppCell>
469
+ * ```
470
+ */
471
+ AppCell: AppCellComponent<TFeatures, TData, NoInfer<TCellComponents>>
472
+ /**
473
+ * Wraps a header and provides header context with pre-bound headerComponents.
474
+ * Optionally accepts a selector for Subscribe functionality.
475
+ * @example
476
+ * ```tsx
477
+ * // Without selector
478
+ * <table.AppHeader header={header}>
479
+ * {(h) => <th><h.SortIndicator /></th>}
480
+ * </table.AppHeader>
481
+ *
482
+ * // With selector
483
+ * <table.AppHeader header={header} selector={(s) => s.sorting}>
484
+ * {(h, sorting) => <th>{sorting.length} sorted</th>}
485
+ * </table.AppHeader>
486
+ * ```
487
+ */
488
+ AppHeader: AppHeaderComponent<TFeatures, TData, NoInfer<THeaderComponents>>
489
+ /**
490
+ * Wraps a footer and provides header context with pre-bound headerComponents.
491
+ * Optionally accepts a selector for Subscribe functionality.
492
+ * @example
493
+ * ```tsx
494
+ * <table.AppFooter header={footer}>
495
+ * {(f) => <td><table.FlexRender footer={footer} /></td>}
496
+ * </table.AppFooter>
497
+ * ```
498
+ */
499
+ AppFooter: AppHeaderComponent<TFeatures, TData, NoInfer<THeaderComponents>>
500
+ }
501
+
502
+ /**
503
+ * Creates a custom table hook with pre-bound components for composition.
504
+ *
505
+ * This is the table equivalent of TanStack Form's `createFormHook`. It allows you to:
506
+ * - Define features, row models, and default options once, shared across all tables
507
+ * - Register reusable table, cell, and header components
508
+ * - Access table/cell/header instances via context in those components
509
+ * - Get a `useAppTable` hook that returns an extended table with App wrapper components
510
+ * - Get a `createAppColumnHelper` function pre-bound to your features
511
+ *
512
+ * @example
513
+ * ```tsx
514
+ * // hooks/table.ts
515
+ * export const {
516
+ * useAppTable,
517
+ * createAppColumnHelper,
518
+ * useTableContext,
519
+ * useCellContext,
520
+ * useHeaderContext,
521
+ * } = createTableHook({
522
+ * features: tableFeatures({
523
+ * rowPaginationFeature,
524
+ * rowSortingFeature,
525
+ * columnFilteringFeature,
526
+ * }),
527
+ * rowModels: {
528
+ * paginatedRowModel: createPaginatedRowModel(),
529
+ * sortedRowModel: createSortedRowModel(sortFns),
530
+ * filteredRowModel: createFilteredRowModel(filterFns),
531
+ * },
532
+ * tableComponents: { PaginationControls, RowCount },
533
+ * cellComponents: { TextCell, NumberCell },
534
+ * headerComponents: { SortIndicator, ColumnFilter },
535
+ * })
536
+ *
537
+ * // Create column helper with TFeatures already bound
538
+ * const columnHelper = createAppColumnHelper<Person>()
539
+ *
540
+ * // components/table-components.tsx
541
+ * function PaginationControls() {
542
+ * const table = useTableContext() // TFeatures already known!
543
+ * return <table.Subscribe selector={(s) => s.pagination}>...</table.Subscribe>
544
+ * }
545
+ *
546
+ * // features/users.tsx
547
+ * function UsersTable({ data }: { data: Person[] }) {
548
+ * const table = useAppTable({
549
+ * columns,
550
+ * data, // TData inferred from Person[]
551
+ * })
552
+ *
553
+ * return (
554
+ * <table.AppTable>
555
+ * <table>
556
+ * <thead>
557
+ * {table.getHeaderGroups().map(headerGroup => (
558
+ * <tr key={headerGroup.id}>
559
+ * {headerGroup.headers.map(h => (
560
+ * <table.AppHeader header={h} key={h.id}>
561
+ * {(header) => (
562
+ * <th>
563
+ * <table.FlexRender header={h} />
564
+ * <header.SortIndicator />
565
+ * </th>
566
+ * )}
567
+ * </table.AppHeader>
568
+ * ))}
569
+ * </tr>
570
+ * ))}
571
+ * </thead>
572
+ * <tbody>
573
+ * {table.getRowModel().rows.map(row => (
574
+ * <tr key={row.id}>
575
+ * {row.getAllCells().map(c => (
576
+ * <table.AppCell cell={c} key={c.id}>
577
+ * {(cell) => <td><cell.TextCell /></td>}
578
+ * </table.AppCell>
579
+ * ))}
580
+ * </tr>
581
+ * ))}
582
+ * </tbody>
583
+ * </table>
584
+ * <table.PaginationControls />
585
+ * </table.AppTable>
586
+ * )
587
+ * }
588
+ * ```
589
+ */
590
+ export function createTableHook<
591
+ TFeatures extends TableFeatures,
592
+ const TTableComponents extends Record<string, ComponentType<any>>,
593
+ const TCellComponents extends Record<string, ComponentType<any>>,
594
+ const THeaderComponents extends Record<string, ComponentType<any>>,
595
+ >({
596
+ tableComponents,
597
+ cellComponents,
598
+ headerComponents,
599
+ ...defaultTableOptions
600
+ }: CreateTableHookOptions<
601
+ TFeatures,
602
+ TTableComponents,
603
+ TCellComponents,
604
+ THeaderComponents
605
+ >) {
606
+ // Create contexts internally with TFeatures baked in
607
+ const TableContext = createContext<ReactTable<TFeatures, any, any>>(
608
+ null as never,
609
+ )
610
+ const CellContext = createContext<Cell<TFeatures, any, any>>(null as never)
611
+ const HeaderContext = createContext<Header<TFeatures, any, any>>(
612
+ null as never,
613
+ )
614
+
615
+ /**
616
+ * Create a column helper pre-bound to the features and components configured in this table hook.
617
+ * The cell, header, and footer contexts include pre-bound components (e.g., `cell.TextCell`).
618
+ * @example
619
+ * ```tsx
620
+ * const columnHelper = createAppColumnHelper<Person>()
621
+ *
622
+ * const columns = [
623
+ * columnHelper.accessor('firstName', {
624
+ * header: 'First Name',
625
+ * cell: ({ cell }) => <cell.TextCell />, // cell has pre-bound components!
626
+ * }),
627
+ * columnHelper.accessor('age', {
628
+ * header: 'Age',
629
+ * cell: ({ cell }) => <cell.NumberCell />,
630
+ * }),
631
+ * ]
632
+ * ```
633
+ */
634
+ function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<
635
+ TFeatures,
636
+ TData,
637
+ TCellComponents,
638
+ THeaderComponents
639
+ > {
640
+ // The runtime implementation is the same - components are attached at render time
641
+ // This cast provides the enhanced types for column definitions
642
+ return coreCreateColumnHelper<TFeatures, TData>() as AppColumnHelper<
643
+ TFeatures,
644
+ TData,
645
+ TCellComponents,
646
+ THeaderComponents
647
+ >
648
+ }
649
+
650
+ /**
651
+ * Access the table instance from within an `AppTable` wrapper.
652
+ * Use this in custom `tableComponents` passed to `createTableHook`.
653
+ * TFeatures is already known from the createTableHook call.
654
+ *
655
+ * @example
656
+ * ```tsx
657
+ * function PaginationControls() {
658
+ * const table = useTableContext()
659
+ * return (
660
+ * <table.Subscribe selector={(s) => s.pagination}>
661
+ * {(pagination) => (
662
+ * <div>
663
+ * <button onClick={() => table.previousPage()}>Prev</button>
664
+ * <span>Page {pagination.pageIndex + 1}</span>
665
+ * <button onClick={() => table.nextPage()}>Next</button>
666
+ * </div>
667
+ * )}
668
+ * </table.Subscribe>
669
+ * )
670
+ * }
671
+ * ```
672
+ */
673
+ function useTableContext<TData extends RowData = RowData>(): ReactTable<
674
+ TFeatures,
675
+ TData
676
+ > {
677
+ // `useContext` keeps React 18 support; `use(Context)` is React 19+ only.
678
+ // eslint-disable-next-line @eslint-react/no-use-context -- intentional for React 18
679
+ const table = useContext(TableContext)
680
+
681
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
682
+ if (!table) {
683
+ throw new Error(
684
+ '`useTableContext` must be used within an `AppTable` component. ' +
685
+ 'Make sure your component is wrapped with `<table.AppTable>...</table.AppTable>`.',
686
+ )
687
+ }
688
+
689
+ return table as ReactTable<TFeatures, TData>
690
+ }
691
+
692
+ /**
693
+ * Access the cell instance from within an `AppCell` wrapper.
694
+ * Use this in custom `cellComponents` passed to `createTableHook`.
695
+ * TFeatures is already known from the createTableHook call.
696
+ *
697
+ * @example
698
+ * ```tsx
699
+ * function TextCell() {
700
+ * const cell = useCellContext<string>()
701
+ * return <span>{cell.getValue()}</span>
702
+ * }
703
+ *
704
+ * function NumberCell({ format }: { format?: Intl.NumberFormatOptions }) {
705
+ * const cell = useCellContext<number>()
706
+ * return <span>{cell.getValue().toLocaleString(undefined, format)}</span>
707
+ * }
708
+ * ```
709
+ */
710
+ function useCellContext<TValue extends CellData = CellData>() {
711
+ // `useContext` keeps React 18 support; `use(Context)` is React 19+ only.
712
+ // eslint-disable-next-line @eslint-react/no-use-context -- intentional for React 18
713
+ const cell = useContext(CellContext)
714
+
715
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
716
+ if (!cell) {
717
+ throw new Error(
718
+ '`useCellContext` must be used within an `AppCell` component. ' +
719
+ 'Make sure your component is wrapped with `<table.AppCell cell={cell}>...</table.AppCell>`.',
720
+ )
721
+ }
722
+
723
+ return cell as Cell<TFeatures, any, TValue>
724
+ }
725
+
726
+ /**
727
+ * Access the header instance from within an `AppHeader` or `AppFooter` wrapper.
728
+ * Use this in custom `headerComponents` passed to `createTableHook`.
729
+ * TFeatures is already known from the createTableHook call.
730
+ *
731
+ * @example
732
+ * ```tsx
733
+ * function SortIndicator() {
734
+ * const header = useHeaderContext()
735
+ * const sorted = header.column.getIsSorted()
736
+ * return sorted === 'asc' ? '🔼' : sorted === 'desc' ? '🔽' : null
737
+ * }
738
+ *
739
+ * function ColumnFilter() {
740
+ * const header = useHeaderContext()
741
+ * if (!header.column.getCanFilter()) return null
742
+ * return (
743
+ * <input
744
+ * value={(header.column.getFilterValue() ?? '') as string}
745
+ * onChange={(e) => header.column.setFilterValue(e.target.value)}
746
+ * placeholder="Filter..."
747
+ * />
748
+ * )
749
+ * }
750
+ * ```
751
+ */
752
+ function useHeaderContext<TValue extends CellData = CellData>() {
753
+ // `useContext` keeps React 18 support; `use(Context)` is React 19+ only.
754
+ // eslint-disable-next-line @eslint-react/no-use-context -- intentional for React 18
755
+ const header = useContext(HeaderContext)
756
+
757
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
758
+ if (!header) {
759
+ throw new Error(
760
+ '`useHeaderContext` must be used within an `AppHeader` or `AppFooter` component.',
761
+ )
762
+ }
763
+
764
+ return header as Header<TFeatures, any, TValue>
765
+ }
766
+
767
+ /**
768
+ * Context-aware FlexRender component for cells.
769
+ * Uses the cell from context, so no need to pass cell prop.
770
+ */
771
+ function CellFlexRender() {
772
+ const cell = useCellContext()
773
+ return <FlexRender cell={cell} />
774
+ }
775
+
776
+ /**
777
+ * Context-aware FlexRender component for headers.
778
+ * Uses the header from context, so no need to pass header prop.
779
+ */
780
+ function HeaderFlexRender() {
781
+ const header = useHeaderContext()
782
+ return <FlexRender header={header} />
783
+ }
784
+
785
+ /**
786
+ * Context-aware FlexRender component for footers.
787
+ * Uses the header from context, so no need to pass footer prop.
788
+ */
789
+ function FooterFlexRender() {
790
+ const header = useHeaderContext()
791
+ return <FlexRender footer={header} />
792
+ }
793
+
794
+ /**
795
+ * Enhanced useTable hook that returns a table with App wrapper components
796
+ * and pre-bound tableComponents attached directly to the table object.
797
+ *
798
+ * Default options from createTableHook are automatically merged with
799
+ * the options passed here. Options passed here take precedence.
800
+ *
801
+ * TFeatures is already known from the createTableHook call; TData is inferred from the data prop.
802
+ */
803
+ function useAppTable<
804
+ TData extends RowData,
805
+ TSelected = TableState<TFeatures>,
806
+ >(
807
+ tableOptions: Omit<
808
+ TableOptions<TFeatures, TData>,
809
+ 'features' | 'rowModels'
810
+ >,
811
+ selector?: (state: TableState<TFeatures>) => TSelected,
812
+ ): AppReactTable<
813
+ TFeatures,
814
+ TData,
815
+ TSelected,
816
+ TTableComponents,
817
+ TCellComponents,
818
+ THeaderComponents
819
+ > {
820
+ // Merge default options with provided options (provided takes precedence)
821
+ const table = useTable<TFeatures, TData, TSelected>(
822
+ { ...defaultTableOptions, ...tableOptions } as TableOptions<
823
+ TFeatures,
824
+ TData
825
+ >,
826
+ selector,
827
+ )
828
+
829
+ // AppTable - Root wrapper that provides table context with optional Subscribe
830
+ const AppTable = useMemo(() => {
831
+ function AppTableImpl(props: AppTablePropsWithoutSelector): ReactNode
832
+ function AppTableImpl<TAppTableSelected>(
833
+ props: AppTablePropsWithSelector<TFeatures, TAppTableSelected>,
834
+ ): ReactNode
835
+ function AppTableImpl<TAppTableSelected>(
836
+ props:
837
+ | AppTablePropsWithoutSelector
838
+ | AppTablePropsWithSelector<TFeatures, TAppTableSelected>,
839
+ ): ReactNode {
840
+ const { children, selector: appTableSelector } = props
841
+
842
+ return (
843
+ <TableContext.Provider value={table}>
844
+ {appTableSelector ? (
845
+ <table.Subscribe selector={appTableSelector}>
846
+ {(state: TAppTableSelected) =>
847
+ (children as (state: TAppTableSelected) => ReactNode)(state)
848
+ }
849
+ </table.Subscribe>
850
+ ) : (
851
+ children
852
+ )}
853
+ </TableContext.Provider>
854
+ )
855
+ }
856
+ return AppTableImpl as AppTableComponent<TFeatures>
857
+ }, [table])
858
+
859
+ // AppCell - Wraps cell with context, pre-bound cellComponents, and optional Subscribe
860
+ const AppCell = useMemo(() => {
861
+ function AppCellImpl<TValue extends CellData = CellData>(
862
+ props: AppCellPropsWithoutSelector<
863
+ TFeatures,
864
+ TData,
865
+ TValue,
866
+ TCellComponents
867
+ >,
868
+ ): ReactNode
869
+ function AppCellImpl<
870
+ TValue extends CellData = CellData,
871
+ TAppCellSelected = unknown,
872
+ >(
873
+ props: AppCellPropsWithSelector<
874
+ TFeatures,
875
+ TData,
876
+ TValue,
877
+ TCellComponents,
878
+ TAppCellSelected
879
+ >,
880
+ ): ReactNode
881
+ function AppCellImpl<
882
+ TValue extends CellData = CellData,
883
+ TAppCellSelected = unknown,
884
+ >(
885
+ props:
886
+ | AppCellPropsWithoutSelector<
887
+ TFeatures,
888
+ TData,
889
+ TValue,
890
+ TCellComponents
891
+ >
892
+ | AppCellPropsWithSelector<
893
+ TFeatures,
894
+ TData,
895
+ TValue,
896
+ TCellComponents,
897
+ TAppCellSelected
898
+ >,
899
+ ): ReactNode {
900
+ const { cell, children, selector: appCellSelector } = props as any
901
+ const extendedCell = Object.assign(cell, {
902
+ FlexRender: CellFlexRender,
903
+ ...cellComponents,
904
+ })
905
+
906
+ return (
907
+ <CellContext.Provider value={cell}>
908
+ {appCellSelector ? (
909
+ <table.Subscribe selector={appCellSelector}>
910
+ {(state: TAppCellSelected) =>
911
+ (
912
+ children as (
913
+ cell: Cell<TFeatures, TData, TValue> &
914
+ TCellComponents & { FlexRender: () => ReactNode },
915
+ state: TAppCellSelected,
916
+ ) => ReactNode
917
+ )(extendedCell, state)
918
+ }
919
+ </table.Subscribe>
920
+ ) : (
921
+ (
922
+ children as (
923
+ cell: Cell<TFeatures, TData, TValue> &
924
+ TCellComponents & { FlexRender: () => ReactNode },
925
+ ) => ReactNode
926
+ )(extendedCell)
927
+ )}
928
+ </CellContext.Provider>
929
+ )
930
+ }
931
+ return AppCellImpl as AppCellComponent<TFeatures, TData, TCellComponents>
932
+ }, [table])
933
+
934
+ // AppHeader - Wraps header with context, pre-bound headerComponents, and optional Subscribe
935
+ const AppHeader = useMemo(() => {
936
+ function AppHeaderImpl<TValue extends CellData = CellData>(
937
+ props: AppHeaderPropsWithoutSelector<
938
+ TFeatures,
939
+ TData,
940
+ TValue,
941
+ THeaderComponents
942
+ >,
943
+ ): ReactNode
944
+ function AppHeaderImpl<
945
+ TValue extends CellData = CellData,
946
+ TAppHeaderSelected = unknown,
947
+ >(
948
+ props: AppHeaderPropsWithSelector<
949
+ TFeatures,
950
+ TData,
951
+ TValue,
952
+ THeaderComponents,
953
+ TAppHeaderSelected
954
+ >,
955
+ ): ReactNode
956
+ function AppHeaderImpl<
957
+ TValue extends CellData = CellData,
958
+ TAppHeaderSelected = unknown,
959
+ >(
960
+ props:
961
+ | AppHeaderPropsWithoutSelector<
962
+ TFeatures,
963
+ TData,
964
+ TValue,
965
+ THeaderComponents
966
+ >
967
+ | AppHeaderPropsWithSelector<
968
+ TFeatures,
969
+ TData,
970
+ TValue,
971
+ THeaderComponents,
972
+ TAppHeaderSelected
973
+ >,
974
+ ): ReactNode {
975
+ const { header, children, selector: appHeaderSelector } = props as any
976
+ const extendedHeader = Object.assign(header, {
977
+ FlexRender: HeaderFlexRender,
978
+ ...headerComponents,
979
+ })
980
+
981
+ return (
982
+ <HeaderContext.Provider value={header}>
983
+ {appHeaderSelector ? (
984
+ <table.Subscribe selector={appHeaderSelector}>
985
+ {(state: TAppHeaderSelected) =>
986
+ (
987
+ children as (
988
+ header: Header<TFeatures, TData, TValue> &
989
+ THeaderComponents & { FlexRender: () => ReactNode },
990
+ state: TAppHeaderSelected,
991
+ ) => ReactNode
992
+ )(extendedHeader, state)
993
+ }
994
+ </table.Subscribe>
995
+ ) : (
996
+ (
997
+ children as (
998
+ header: Header<TFeatures, TData, TValue> &
999
+ THeaderComponents & { FlexRender: () => ReactNode },
1000
+ ) => ReactNode
1001
+ )(extendedHeader)
1002
+ )}
1003
+ </HeaderContext.Provider>
1004
+ )
1005
+ }
1006
+ return AppHeaderImpl as AppHeaderComponent<
1007
+ TFeatures,
1008
+ TData,
1009
+ THeaderComponents
1010
+ >
1011
+ }, [table])
1012
+
1013
+ // AppFooter - Same as AppHeader (footers use Header type)
1014
+ const AppFooter = useMemo(() => {
1015
+ function AppFooterImpl<TValue extends CellData = CellData>(
1016
+ props: AppHeaderPropsWithoutSelector<
1017
+ TFeatures,
1018
+ TData,
1019
+ TValue,
1020
+ THeaderComponents
1021
+ >,
1022
+ ): ReactNode
1023
+ function AppFooterImpl<
1024
+ TValue extends CellData = CellData,
1025
+ TAppFooterSelected = unknown,
1026
+ >(
1027
+ props: AppHeaderPropsWithSelector<
1028
+ TFeatures,
1029
+ TData,
1030
+ TValue,
1031
+ THeaderComponents,
1032
+ TAppFooterSelected
1033
+ >,
1034
+ ): ReactNode
1035
+ function AppFooterImpl<
1036
+ TValue extends CellData = CellData,
1037
+ TAppFooterSelected = unknown,
1038
+ >(
1039
+ props:
1040
+ | AppHeaderPropsWithoutSelector<
1041
+ TFeatures,
1042
+ TData,
1043
+ TValue,
1044
+ THeaderComponents
1045
+ >
1046
+ | AppHeaderPropsWithSelector<
1047
+ TFeatures,
1048
+ TData,
1049
+ TValue,
1050
+ THeaderComponents,
1051
+ TAppFooterSelected
1052
+ >,
1053
+ ): ReactNode {
1054
+ const { header, children, selector: appFooterSelector } = props as any
1055
+ const extendedHeader = Object.assign(header, {
1056
+ FlexRender: FooterFlexRender,
1057
+ ...headerComponents,
1058
+ })
1059
+
1060
+ return (
1061
+ <HeaderContext.Provider value={header}>
1062
+ {appFooterSelector ? (
1063
+ <table.Subscribe selector={appFooterSelector}>
1064
+ {(state: TAppFooterSelected) =>
1065
+ (
1066
+ children as (
1067
+ header: Header<TFeatures, TData, TValue> &
1068
+ THeaderComponents & { FlexRender: () => ReactNode },
1069
+ state: TAppFooterSelected,
1070
+ ) => ReactNode
1071
+ )(extendedHeader, state)
1072
+ }
1073
+ </table.Subscribe>
1074
+ ) : (
1075
+ (
1076
+ children as (
1077
+ header: Header<TFeatures, TData, TValue> &
1078
+ THeaderComponents & { FlexRender: () => ReactNode },
1079
+ ) => ReactNode
1080
+ )(extendedHeader)
1081
+ )}
1082
+ </HeaderContext.Provider>
1083
+ )
1084
+ }
1085
+ return AppFooterImpl as AppHeaderComponent<
1086
+ TFeatures,
1087
+ TData,
1088
+ THeaderComponents
1089
+ >
1090
+ }, [table])
1091
+
1092
+ // Combine everything into the extended table API
1093
+ const extendedTable = useMemo(() => {
1094
+ return Object.assign(table, {
1095
+ AppTable,
1096
+ AppCell,
1097
+ AppHeader,
1098
+ AppFooter,
1099
+ ...tableComponents,
1100
+ }) as AppReactTable<
1101
+ TFeatures,
1102
+ TData,
1103
+ TSelected,
1104
+ TTableComponents,
1105
+ TCellComponents,
1106
+ THeaderComponents
1107
+ >
1108
+ }, [table, AppTable, AppCell, AppHeader, AppFooter])
1109
+
1110
+ return extendedTable
1111
+ }
1112
+
1113
+ return {
1114
+ appFeatures: defaultTableOptions.features as TFeatures,
1115
+ createAppColumnHelper,
1116
+ useAppTable,
1117
+ useTableContext,
1118
+ useCellContext,
1119
+ useHeaderContext,
1120
+ }
1121
+ }