@tanstack/ember-table 9.0.0-beta.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 (46) hide show
  1. package/README.md +128 -0
  2. package/addon-main.cjs +4 -0
  3. package/declarations/FlexRender.d.ts +63 -0
  4. package/declarations/FlexRender.d.ts.map +1 -0
  5. package/declarations/create-table-hook.d.ts +33 -0
  6. package/declarations/create-table-hook.d.ts.map +1 -0
  7. package/declarations/experimental-worker-plugin.d.ts +2 -0
  8. package/declarations/experimental-worker-plugin.d.ts.map +1 -0
  9. package/declarations/flex-render-helpers.d.ts +25 -0
  10. package/declarations/flex-render-helpers.d.ts.map +1 -0
  11. package/declarations/flex-render.d.ts +3 -0
  12. package/declarations/flex-render.d.ts.map +1 -0
  13. package/declarations/index.d.ts +10 -0
  14. package/declarations/index.d.ts.map +1 -0
  15. package/declarations/reactivity.d.ts +3 -0
  16. package/declarations/reactivity.d.ts.map +1 -0
  17. package/declarations/signal.d.ts +38 -0
  18. package/declarations/signal.d.ts.map +1 -0
  19. package/declarations/static-functions.d.ts +2 -0
  20. package/declarations/static-functions.d.ts.map +1 -0
  21. package/declarations/use-table.d.ts +3 -0
  22. package/declarations/use-table.d.ts.map +1 -0
  23. package/dist/FlexRender-fkWv7pUy.js +128 -0
  24. package/dist/FlexRender-fkWv7pUy.js.map +1 -0
  25. package/dist/experimental-worker-plugin.js +2 -0
  26. package/dist/experimental-worker-plugin.js.map +1 -0
  27. package/dist/flex-render.js +3 -0
  28. package/dist/flex-render.js.map +1 -0
  29. package/dist/index.js +286 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/static-functions.js +2 -0
  32. package/dist/static-functions.js.map +1 -0
  33. package/package.json +147 -0
  34. package/skills/create-table-hook/SKILL.md +117 -0
  35. package/skills/getting-started/SKILL.md +160 -0
  36. package/skills/table-state/SKILL.md +187 -0
  37. package/src/FlexRender.gts +267 -0
  38. package/src/create-table-hook.ts +83 -0
  39. package/src/experimental-worker-plugin.ts +1 -0
  40. package/src/flex-render-helpers.ts +103 -0
  41. package/src/flex-render.ts +6 -0
  42. package/src/index.ts +31 -0
  43. package/src/reactivity.ts +39 -0
  44. package/src/signal.ts +126 -0
  45. package/src/static-functions.ts +1 -0
  46. package/src/use-table.ts +154 -0
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: getting-started
3
+ description: >
4
+ Create a TanStack Ember Table v9 table with useTable, a tracked options thunk, stable tableFeatures and columns, .gts templates, FlexRenderCell/Header/Footer, and correctly bound template helpers. Load for first-table setup, Glimmer reactivity, component cell renderers, or adapting another framework's example to Ember.
5
+ metadata:
6
+ type: framework
7
+ library: '@tanstack/ember-table'
8
+ framework: ember
9
+ library_version: '9.0.0-beta.38'
10
+ requires:
11
+ - '@tanstack/table-core#core'
12
+ - '@tanstack/table-core#table-features'
13
+ sources:
14
+ - 'TanStack/table:docs/framework/ember/quick-start.md'
15
+ - 'TanStack/table:examples/ember/basic-table'
16
+ - 'TanStack/table:packages/ember-table/src/index.ts'
17
+ - 'TanStack/table:packages/ember-table/src/use-table.ts'
18
+ - 'TanStack/table:packages/ember-table/src/FlexRender.gts'
19
+ ---
20
+
21
+ This skill builds on `@tanstack/table-core#core` and `@tanstack/table-core#table-features`. Ember Table is headless: it supplies reactive table models and renderer helpers, while the application owns semantic markup, CSS, accessibility, and design-system components.
22
+
23
+ The v9 addon requires Ember 5.8 or newer with Embroider or ember-auto-import v2. Prefer `.gts`/`.gjs` template-tag components with Glint.
24
+
25
+ ## Setup
26
+
27
+ ```gts
28
+ import Component from '@glimmer/component'
29
+ import { tracked } from '@glimmer/tracking'
30
+ import { on } from '@ember/modifier'
31
+ import {
32
+ FlexRenderCell,
33
+ FlexRenderHeader,
34
+ createColumnHelper,
35
+ tableFeatures,
36
+ useTable,
37
+ type Cell,
38
+ type Row,
39
+ } from '@tanstack/ember-table'
40
+
41
+ type Person = { id: string; name: string }
42
+
43
+ const features = tableFeatures({})
44
+ const columnHelper = createColumnHelper<typeof features, Person>()
45
+ const columns = columnHelper.columns([
46
+ columnHelper.accessor('name', { header: 'Name' }),
47
+ ])
48
+ const initialData: Person[] = [{ id: '1', name: 'Ada' }]
49
+ const getRowId = (row: Person) => row.id
50
+
51
+ const getAllCells = (
52
+ row: Row<typeof features, Person>,
53
+ ): Array<Cell<typeof features, Person>> => row.getAllCells()
54
+
55
+ export default class PeopleTable extends Component {
56
+ @tracked data = initialData
57
+
58
+ table = useTable(() => ({
59
+ features,
60
+ columns,
61
+ data: this.data,
62
+ getRowId,
63
+ }))
64
+
65
+ get headerGroups() {
66
+ return this.table.getHeaderGroups()
67
+ }
68
+
69
+ get rows() {
70
+ return this.table.getRowModel().rows
71
+ }
72
+
73
+ addPerson = () => {
74
+ this.data = [...this.data, { id: '2', name: 'Grace' }]
75
+ }
76
+
77
+ <template>
78
+ <button type='button' {{on 'click' this.addPerson}}>Add person</button>
79
+ <table>
80
+ <thead>
81
+ {{#each this.headerGroups as |group|}}
82
+ <tr>
83
+ {{#each group.headers as |header|}}
84
+ <th colspan={{header.colSpan}}>
85
+ {{#unless header.isPlaceholder}}
86
+ <FlexRenderHeader @header={{header}} />
87
+ {{/unless}}
88
+ </th>
89
+ {{/each}}
90
+ </tr>
91
+ {{/each}}
92
+ </thead>
93
+ <tbody>
94
+ {{#each this.rows as |row|}}
95
+ <tr>
96
+ {{#each (getAllCells row) as |cell|}}
97
+ <td><FlexRenderCell @cell={{cell}} /></td>
98
+ {{/each}}
99
+ </tr>
100
+ {{/each}}
101
+ </tbody>
102
+ </table>
103
+ </template>
104
+ }
105
+ ```
106
+
107
+ `useTable` takes an options thunk. Tracked properties read by that thunk update table options, and table API reads inside getters/templates participate in Glimmer tracking. Keep `features` and `columns` at stable module or component-lifetime scope; replace `@tracked data` only when data meaningfully changes.
108
+
109
+ ## Ember-Specific Patterns
110
+
111
+ ### Preserve method receivers in templates
112
+
113
+ V9 table, column, row, cell, and header methods live on prototypes and require their receiver. Ember templates extract function references, so call methods in a getter or a small module-level helper:
114
+
115
+ ```gts
116
+ const getCanSort = (column: Column<typeof features, Person>) =>
117
+ column.getCanSort()
118
+
119
+ const toggleSort =
120
+ (column: Column<typeof features, Person>) => (event: Event) =>
121
+ column.getToggleSortingHandler()?.(event)
122
+ ```
123
+
124
+ Pass `toggleSort header.column` to `{{on}}`; do not pass an extracted Table method directly.
125
+
126
+ ### Render definitions through the matching component
127
+
128
+ Use `FlexRenderCell`, `FlexRenderHeader`, and `FlexRenderFooter` with their matching object. A definition may return a primitive or `flexRenderComponent(Component, options)`. The rendered component receives `@ctx` and optional `@options`; Table does not instantiate arbitrary component-library markup for you.
129
+
130
+ ## Common Mistakes
131
+
132
+ ### HIGH Passing an options object instead of a thunk
133
+
134
+ Wrong: `useTable({ features, columns, data: this.data })`.
135
+
136
+ Correct: `useTable(() => ({ features, columns, data: this.data }))`.
137
+
138
+ The thunk is how tracked option reads are connected to the table.
139
+
140
+ ### HIGH Passing an unbound prototype method to a template modifier
141
+
142
+ Wrong: `{{on 'click' header.column.getToggleSortingHandler}}`.
143
+
144
+ Correct: wrap the call in a helper that invokes the method on `header.column`, as shown above.
145
+
146
+ An extracted v9 prototype method loses `this` and can throw or silently target the wrong receiver.
147
+
148
+ ### HIGH Recreating model inputs inside the options thunk
149
+
150
+ Wrong: `columns: columnHelper.columns(...)` or `data: this.data.slice()` inside `useTable(() => ...)`.
151
+
152
+ Correct: keep columns stable and pass the tracked data reference directly. The thunk may rerun; it should not manufacture new model inputs on every tracked update.
153
+
154
+ ### MEDIUM Using a visibility-aware API without its feature
155
+
156
+ With `tableFeatures({})`, render `row.getAllCells()`. Add `columnVisibilityFeature` before using `row.getVisibleCells()`.
157
+
158
+ ## API Discovery
159
+
160
+ Inspect `node_modules/@tanstack/ember-table/src/index.ts` for exports, `use-table.ts` for options/reactivity behavior, `FlexRender.gts` and `flex-render.ts` for renderer contracts, and `node_modules/@tanstack/table-core/src/features/<feature>/` for feature-gated APIs. Do not substitute React hooks, subscriptions, or component signatures.
@@ -0,0 +1,187 @@
1
+ ---
2
+ name: table-state
3
+ description: >
4
+ Read, track, initialize, control, and reset TanStack Ember Table v9 state through Glimmer-reactive table APIs, baseAtoms, atoms, store, Ember createAtom, or @tracked state plus on*Change. Load for ownership precedence, updater callbacks, stale template state, options-thunk reactivity, or incorrect table.Subscribe/store.subscribe usage.
5
+ metadata:
6
+ type: framework
7
+ library: '@tanstack/ember-table'
8
+ framework: ember
9
+ library_version: '9.0.0-beta.38'
10
+ requires:
11
+ - '@tanstack/table-core#core'
12
+ - getting-started
13
+ sources:
14
+ - 'TanStack/table:docs/framework/ember/guide/table-state.md'
15
+ - 'TanStack/table:examples/ember/basic-external-atoms'
16
+ - 'TanStack/table:examples/ember/basic-external-state'
17
+ - 'TanStack/table:packages/ember-table/src/use-table.ts'
18
+ - 'TanStack/table:packages/ember-table/src/reactivity.ts'
19
+ - 'TanStack/table:packages/ember-table/src/signal.ts'
20
+ ---
21
+
22
+ This skill builds on `@tanstack/table-core#core` and `getting-started`. Keep state internal unless another part of the application must read, persist, validate, or drive a slice.
23
+
24
+ ## State Surfaces
25
+
26
+ State exists only for registered features:
27
+
28
+ - `table.baseAtoms` are the table's internal writable atoms.
29
+ - `table.atoms` are readonly, ownership-aware atoms for each registered slice.
30
+ - `table.store.state` exposes the current flat state.
31
+ - Feature methods such as `setSorting`, `setPageSize`, and `row.toggleSelected()` are the preferred write path.
32
+
33
+ Ember's reactivity feature bridges these reads into Glimmer tracking. There is no `table.Subscribe`, no selector argument to `useTable`, and no need to subscribe during rendering. Read the relevant API inside a template-consumed getter:
34
+
35
+ ```gts
36
+ get rows() {
37
+ return this.table.getRowModel().rows
38
+ }
39
+
40
+ get pageIndex() {
41
+ return this.table.atoms.pagination.get().pageIndex
42
+ }
43
+ ```
44
+
45
+ `table.store.subscribe` is not the Ember rendering mechanism. The adapter's store is pull-based; Glimmer tracks individual property and atom reads.
46
+
47
+ `features` and `atoms` are construct-time inputs in this adapter. Create them once before `useTable`; rerunning the options thunk updates ordinary live options but does not swap the table's feature set or atom owners.
48
+
49
+ ## Choose One Owner Per Slice
50
+
51
+ For a slice such as pagination, choose one source of truth:
52
+
53
+ 1. Internal `baseAtoms` by default.
54
+ 2. `initialState.pagination` for an internally owned starting value.
55
+ 3. An external writable atom in `atoms.pagination`.
56
+ 4. A `@tracked` value in `state.pagination`, paired with `onPaginationChange`.
57
+
58
+ Read precedence is `atoms[key]` over `state[key]` over the internal `baseAtoms[key]`. Do not configure multiple owners merely as fallbacks.
59
+
60
+ ## Internal and Initial State
61
+
62
+ With no `initialState`, `atoms`, `state`, or `on[State]Change`, Table owns registered state internally. Use `initialState` when only the starting/reset value differs:
63
+
64
+ ```gts
65
+ table = useTable(() => ({
66
+ features,
67
+ columns,
68
+ data: this.data,
69
+ initialState: {
70
+ pagination: { pageIndex: 0, pageSize: 25 },
71
+ },
72
+ }))
73
+ ```
74
+
75
+ Changing `initialState` later does not reset existing state. Prefer feature reset methods such as `resetPagination()`; passing `true` requests that feature's blank/default state. Core `table.reset()` resets internal base atoms and is not the primary reset path for externally owned atoms.
76
+
77
+ ## External Ember Atoms
78
+
79
+ Use the adapter's re-exported `createAtom` when a slice should be shared or read directly outside Table. It satisfies the TanStack Store atom contract and its reads are Glimmer-reactive:
80
+
81
+ ```gts
82
+ import {
83
+ createAtom,
84
+ useTable,
85
+ type PaginationState,
86
+ } from '@tanstack/ember-table'
87
+
88
+ paginationAtom = createAtom<PaginationState>({
89
+ pageIndex: 0,
90
+ pageSize: 10,
91
+ })
92
+
93
+ table = useTable(() => ({
94
+ features,
95
+ columns,
96
+ data: this.data,
97
+ atoms: { pagination: this.paginationAtom },
98
+ }))
99
+
100
+ get pagination() {
101
+ return this.paginationAtom.get()
102
+ }
103
+ ```
104
+
105
+ Feature APIs write through the external atom, so do not add `onPaginationChange` for the same atom-owned slice. Write the external atom itself when using the low-level path.
106
+
107
+ ## Controlled Tracked State
108
+
109
+ Use `state` plus the matching callback when a `@tracked` property owns a slice. Resolve both raw values and updater functions:
110
+
111
+ ```gts
112
+ import {
113
+ createPaginatedRowModel,
114
+ rowPaginationFeature,
115
+ tableFeatures,
116
+ type PaginationState,
117
+ } from '@tanstack/ember-table'
118
+
119
+ const features = tableFeatures({
120
+ rowPaginationFeature,
121
+ paginatedRowModel: createPaginatedRowModel(),
122
+ })
123
+
124
+ @tracked pagination: PaginationState = { pageIndex: 0, pageSize: 10 }
125
+
126
+ table = useTable(() => ({
127
+ features,
128
+ columns,
129
+ data: this.data,
130
+ state: { pagination: this.pagination },
131
+ onPaginationChange: (updater) => {
132
+ this.pagination =
133
+ typeof updater === 'function' ? updater(this.pagination) : updater
134
+ },
135
+ }))
136
+ ```
137
+
138
+ The pagination feature owns the state and APIs; the row-model slot enables client-side pagination. For server-owned pagination, keep `rowPaginationFeature`, omit the client row-model slot, and configure the matching manual/server data options. The options thunk must read `this.pagination`; assigning the tracked field reruns the thunk and feeds the controlled value back into Table. V9 has no global `onStateChange` option.
139
+
140
+ ## Low-Level Writes and Types
141
+
142
+ Use a feature method first. If internally owned state truly needs a low-level update:
143
+
144
+ ```ts
145
+ this.table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
146
+ ```
147
+
148
+ Do not write `table.atoms.pagination`; it is readonly and may point at an external owner. Infer the complete registered shape with `TableState<typeof features>` and use feature-specific types such as `PaginationState` or `SortingState` for owned fields.
149
+
150
+ ## Common Mistakes
151
+
152
+ ### CRITICAL Controlling a slice without writing callbacks back
153
+
154
+ Wrong:
155
+
156
+ ```gts
157
+ state: { pagination: this.pagination },
158
+ onPaginationChange: () => {},
159
+ ```
160
+
161
+ Correct: apply the value-or-updater to the tracked owner. Otherwise the slice is frozen at the controlled value.
162
+
163
+ ### HIGH Expecting a snapshot read or subscription component
164
+
165
+ Wrong: invent `<table.Subscribe>`, call `table.store.subscribe(...)` for rendering, or cache `table.store.state.pagination` outside a tracked getter.
166
+
167
+ Correct: read `table.atoms.pagination.get()`, `table.store.state.pagination`, or the relevant Table API inside a template-consumed getter.
168
+
169
+ `table.getState()` is removed v8 API, not an Ember state-read alternative.
170
+
171
+ ### HIGH Mixing atom and state ownership
172
+
173
+ If both `atoms.pagination` and `state.pagination` are present, the atom wins. Choose one owner so resets and writes have an unambiguous destination.
174
+
175
+ ### HIGH Mutating controlled objects in place
176
+
177
+ Wrong: `this.pagination.pageIndex++`.
178
+
179
+ Correct: assign a new tracked value or let `table.setPageIndex(...)` invoke the controlled updater. Glimmer and the options thunk need an observable owner update.
180
+
181
+ ### MEDIUM Writing the internal base atom for an external owner
182
+
183
+ When `atoms.pagination` owns the slice, changing `baseAtoms.pagination` does not replace the active value. Write the external atom or use the feature API.
184
+
185
+ ## API Discovery
186
+
187
+ Inspect `node_modules/@tanstack/ember-table/src/use-table.ts` for option/state precedence and the Glimmer bridge, `signal.ts` for `createAtom` behavior, and `reactivity.ts` for scheduling/tracking. Inspect the registered feature under `node_modules/@tanstack/table-core/src/features/<feature>/` for its state type, update API, and reset semantics.
@@ -0,0 +1,267 @@
1
+ import Component from '@glimmer/component'
2
+ import { FlexRenderComponentConfig } from './flex-render-helpers.ts'
3
+ import { flexRender } from '@tanstack/table-core/flex-render'
4
+ import type {
5
+ Cell_Core,
6
+ CellContext,
7
+ CellData,
8
+ Header_Core,
9
+ HeaderContext,
10
+ RowData,
11
+ TableFeatures,
12
+ } from '@tanstack/table-core'
13
+ import type { ComponentLike, ContentValue } from '@glint/template'
14
+
15
+ type RenderOptions = Record<string, unknown> | undefined
16
+
17
+ type CellRenderResult<
18
+ TFeatures extends TableFeatures,
19
+ TData extends RowData,
20
+ TValue extends CellData = CellData,
21
+ > =
22
+ | string
23
+ | number
24
+ | null
25
+ | FlexRenderComponentConfig<TFeatures, TData, TValue, RenderOptions>
26
+
27
+ type HeaderRenderResult<
28
+ TFeatures extends TableFeatures,
29
+ TData extends RowData,
30
+ TValue extends CellData = CellData,
31
+ > =
32
+ | string
33
+ | number
34
+ | null
35
+ | FlexRenderComponentConfig<TFeatures, TData, TValue, RenderOptions>
36
+
37
+ interface CellRenderSignature<
38
+ TFeatures extends TableFeatures,
39
+ TData extends RowData,
40
+ TValue extends CellData,
41
+ > {
42
+ Args: {
43
+ ctx: CellContext<TFeatures, TData, TValue>
44
+ options?: Record<string, unknown>
45
+ }
46
+ }
47
+
48
+ interface HeaderRenderSignature<
49
+ TFeatures extends TableFeatures,
50
+ TData extends RowData,
51
+ TValue extends CellData,
52
+ > {
53
+ Args: {
54
+ ctx: HeaderContext<TFeatures, TData, TValue>
55
+ options?: Record<string, unknown>
56
+ }
57
+ }
58
+
59
+ // --- FlexRenderCell ---
60
+
61
+ export interface FlexRenderCellSignature<
62
+ TFeatures extends TableFeatures,
63
+ TData extends RowData,
64
+ TValue extends CellData = CellData,
65
+ > {
66
+ Args: {
67
+ cell: Cell_Core<TFeatures, TData, TValue>
68
+ }
69
+ Element: null
70
+ }
71
+
72
+ export class FlexRenderCell<
73
+ TFeatures extends TableFeatures,
74
+ TData extends RowData,
75
+ TValue extends CellData = CellData,
76
+ > extends Component<FlexRenderCellSignature<TFeatures, TData, TValue>> {
77
+ get result(): CellRenderResult<TFeatures, TData, TValue> {
78
+ const cell = this.args.cell
79
+ return flexRender(
80
+ cell.column.columnDef.cell,
81
+ cell.getContext(),
82
+ ) as CellRenderResult<TFeatures, TData, TValue>
83
+ }
84
+
85
+ get resolvedContext(): CellContext<TFeatures, TData, TValue> {
86
+ return this.args.cell.getContext()
87
+ }
88
+
89
+ get isComponent(): boolean {
90
+ return this.result instanceof FlexRenderComponentConfig
91
+ }
92
+
93
+ get componentToRender():
94
+ | ComponentLike<CellRenderSignature<TFeatures, TData, TValue>>
95
+ | undefined {
96
+ const result = this.result
97
+ if (result instanceof FlexRenderComponentConfig) {
98
+ return result.component
99
+ }
100
+ return undefined
101
+ }
102
+
103
+ get componentOptions(): RenderOptions {
104
+ const result = this.result
105
+ if (result instanceof FlexRenderComponentConfig) {
106
+ return result.options
107
+ }
108
+ return undefined
109
+ }
110
+
111
+ get content(): ContentValue {
112
+ return this.result as ContentValue
113
+ }
114
+
115
+ <template>
116
+ {{#if this.isComponent}}
117
+ <this.componentToRender
118
+ @ctx={{this.resolvedContext}}
119
+ @options={{this.componentOptions}}
120
+ />
121
+ {{else}}
122
+ {{this.content}}
123
+ {{/if}}
124
+ </template>
125
+ }
126
+
127
+ // --- FlexRenderHeader ---
128
+
129
+ export interface FlexRenderHeaderSignature<
130
+ TFeatures extends TableFeatures,
131
+ TData extends RowData,
132
+ TValue extends CellData = CellData,
133
+ > {
134
+ Args: {
135
+ header: Header_Core<TFeatures, TData, TValue>
136
+ }
137
+ Element: null
138
+ }
139
+
140
+ export class FlexRenderHeader<
141
+ TFeatures extends TableFeatures,
142
+ TData extends RowData,
143
+ TValue extends CellData = CellData,
144
+ > extends Component<FlexRenderHeaderSignature<TFeatures, TData, TValue>> {
145
+ get result(): HeaderRenderResult<TFeatures, TData, TValue> {
146
+ const header = this.args.header
147
+ if (header.isPlaceholder) return null
148
+ return flexRender(
149
+ header.column.columnDef.header,
150
+ header.getContext(),
151
+ ) as HeaderRenderResult<TFeatures, TData, TValue>
152
+ }
153
+
154
+ get resolvedContext(): HeaderContext<TFeatures, TData, TValue> {
155
+ return this.args.header.getContext()
156
+ }
157
+
158
+ get isComponent(): boolean {
159
+ return this.result instanceof FlexRenderComponentConfig
160
+ }
161
+
162
+ get componentToRender():
163
+ | ComponentLike<HeaderRenderSignature<TFeatures, TData, TValue>>
164
+ | undefined {
165
+ const result = this.result
166
+ if (result instanceof FlexRenderComponentConfig) {
167
+ return result.component as unknown as ComponentLike<
168
+ HeaderRenderSignature<TFeatures, TData, TValue>
169
+ >
170
+ }
171
+ return undefined
172
+ }
173
+
174
+ get componentOptions(): RenderOptions {
175
+ const result = this.result
176
+ if (result instanceof FlexRenderComponentConfig) {
177
+ return result.options
178
+ }
179
+ return undefined
180
+ }
181
+
182
+ get content(): ContentValue {
183
+ return this.result as ContentValue
184
+ }
185
+
186
+ <template>
187
+ {{#if this.isComponent}}
188
+ <this.componentToRender
189
+ @ctx={{this.resolvedContext}}
190
+ @options={{this.componentOptions}}
191
+ />
192
+ {{else}}
193
+ {{this.content}}
194
+ {{/if}}
195
+ </template>
196
+ }
197
+
198
+ // --- FlexRenderFooter ---
199
+
200
+ export interface FlexRenderFooterSignature<
201
+ TFeatures extends TableFeatures,
202
+ TData extends RowData,
203
+ TValue extends CellData = CellData,
204
+ > {
205
+ Args: {
206
+ footer: Header_Core<TFeatures, TData, TValue>
207
+ }
208
+ Element: null
209
+ }
210
+
211
+ export class FlexRenderFooter<
212
+ TFeatures extends TableFeatures,
213
+ TData extends RowData,
214
+ TValue extends CellData = CellData,
215
+ > extends Component<FlexRenderFooterSignature<TFeatures, TData, TValue>> {
216
+ get result(): HeaderRenderResult<TFeatures, TData, TValue> {
217
+ const footer = this.args.footer
218
+ if (footer.isPlaceholder) return null
219
+ return flexRender(
220
+ footer.column.columnDef.footer,
221
+ footer.getContext(),
222
+ ) as HeaderRenderResult<TFeatures, TData, TValue>
223
+ }
224
+
225
+ get resolvedContext(): HeaderContext<TFeatures, TData, TValue> {
226
+ return this.args.footer.getContext()
227
+ }
228
+
229
+ get isComponent(): boolean {
230
+ return this.result instanceof FlexRenderComponentConfig
231
+ }
232
+
233
+ get componentToRender():
234
+ | ComponentLike<HeaderRenderSignature<TFeatures, TData, TValue>>
235
+ | undefined {
236
+ const result = this.result
237
+ if (result instanceof FlexRenderComponentConfig) {
238
+ return result.component as unknown as ComponentLike<
239
+ HeaderRenderSignature<TFeatures, TData, TValue>
240
+ >
241
+ }
242
+ return undefined
243
+ }
244
+
245
+ get componentOptions(): RenderOptions {
246
+ const result = this.result
247
+ if (result instanceof FlexRenderComponentConfig) {
248
+ return result.options
249
+ }
250
+ return undefined
251
+ }
252
+
253
+ get content(): ContentValue {
254
+ return this.result as ContentValue
255
+ }
256
+
257
+ <template>
258
+ {{#if this.isComponent}}
259
+ <this.componentToRender
260
+ @ctx={{this.resolvedContext}}
261
+ @options={{this.componentOptions}}
262
+ />
263
+ {{else}}
264
+ {{this.content}}
265
+ {{/if}}
266
+ </template>
267
+ }
@@ -0,0 +1,83 @@
1
+ import { createColumnHelper as coreCreateColumnHelper } from '@tanstack/table-core'
2
+ import { useTable } from './use-table.ts'
3
+ import type {
4
+ RowData,
5
+ Table,
6
+ TableFeatures,
7
+ TableOptions,
8
+ } from '@tanstack/table-core'
9
+
10
+ export type CreateTableHookOptions<TFeatures extends TableFeatures> = Omit<
11
+ // `any` (not `RowData`) is intentional and matches the other adapters'
12
+ // createTableHook: shared defaults are declared before any specific TData is
13
+ // known, and TData-dependent options like `getRowId` and `meta` are
14
+ // contravariant in TData, so a narrower type would reject them here.
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ TableOptions<TFeatures, any>,
17
+ 'columns' | 'data' | 'state'
18
+ >
19
+
20
+ export type AppEmberTable<
21
+ TFeatures extends TableFeatures,
22
+ TData extends RowData,
23
+ > = Table<TFeatures, TData>
24
+
25
+ export type AppColumnHelper<
26
+ TFeatures extends TableFeatures,
27
+ TData extends RowData,
28
+ > = ReturnType<typeof coreCreateColumnHelper<TFeatures, TData>>
29
+
30
+ /**
31
+ * Bundles a feature set and shared default options once so every table in your
32
+ * app can be created without repeating them. Returns a typed column helper
33
+ * factory and a `createAppTable` that wraps {@link useTable}.
34
+ *
35
+ * Unlike the React adapter's hook, this does not pre-bind cell/header
36
+ * components onto the table; render with the `FlexRenderCell`,
37
+ * `FlexRenderHeader`, and `FlexRenderFooter` components as usual.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const { createAppTable, createAppColumnHelper } = createTableHook({
42
+ * features: tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns }),
43
+ * })
44
+ *
45
+ * const columnHelper = createAppColumnHelper<Person>()
46
+ * const columns = columnHelper.columns([...])
47
+ *
48
+ * // inside a Glimmer component; options stay a thunk so tracked reads are reactive
49
+ * table = createAppTable(() => ({ columns, data: this.data }))
50
+ * ```
51
+ */
52
+ export function createTableHook<TFeatures extends TableFeatures>({
53
+ ...defaultTableOptions
54
+ }: CreateTableHookOptions<TFeatures>) {
55
+ function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<
56
+ TFeatures,
57
+ TData
58
+ > {
59
+ return coreCreateColumnHelper<TFeatures, TData>()
60
+ }
61
+
62
+ function createAppTable<TData extends RowData>(
63
+ getTableOptions: () => Omit<TableOptions<TFeatures, TData>, 'features'>,
64
+ ): AppEmberTable<TFeatures, TData> {
65
+ // Keep options a thunk: the merge runs inside `useTable`'s options thunk,
66
+ // so tracked properties read in `getTableOptions` stay reactive. Per-table
67
+ // options take precedence over the shared defaults (except `features`,
68
+ // which only the hook provides).
69
+ return useTable<TFeatures, TData>(
70
+ () =>
71
+ ({
72
+ ...defaultTableOptions,
73
+ ...getTableOptions(),
74
+ }) as TableOptions<TFeatures, TData>,
75
+ )
76
+ }
77
+
78
+ return {
79
+ appFeatures: defaultTableOptions.features as TFeatures,
80
+ createAppColumnHelper,
81
+ createAppTable,
82
+ }
83
+ }
@@ -0,0 +1 @@
1
+ export * from '@tanstack/table-core/experimental-worker-plugin'