@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.
- package/README.md +128 -0
- package/addon-main.cjs +4 -0
- package/declarations/FlexRender.d.ts +63 -0
- package/declarations/FlexRender.d.ts.map +1 -0
- package/declarations/create-table-hook.d.ts +33 -0
- package/declarations/create-table-hook.d.ts.map +1 -0
- package/declarations/experimental-worker-plugin.d.ts +2 -0
- package/declarations/experimental-worker-plugin.d.ts.map +1 -0
- package/declarations/flex-render-helpers.d.ts +25 -0
- package/declarations/flex-render-helpers.d.ts.map +1 -0
- package/declarations/flex-render.d.ts +3 -0
- package/declarations/flex-render.d.ts.map +1 -0
- package/declarations/index.d.ts +10 -0
- package/declarations/index.d.ts.map +1 -0
- package/declarations/reactivity.d.ts +3 -0
- package/declarations/reactivity.d.ts.map +1 -0
- package/declarations/signal.d.ts +38 -0
- package/declarations/signal.d.ts.map +1 -0
- package/declarations/static-functions.d.ts +2 -0
- package/declarations/static-functions.d.ts.map +1 -0
- package/declarations/use-table.d.ts +3 -0
- package/declarations/use-table.d.ts.map +1 -0
- package/dist/FlexRender-fkWv7pUy.js +128 -0
- package/dist/FlexRender-fkWv7pUy.js.map +1 -0
- package/dist/experimental-worker-plugin.js +2 -0
- package/dist/experimental-worker-plugin.js.map +1 -0
- package/dist/flex-render.js +3 -0
- package/dist/flex-render.js.map +1 -0
- package/dist/index.js +286 -0
- package/dist/index.js.map +1 -0
- package/dist/static-functions.js +2 -0
- package/dist/static-functions.js.map +1 -0
- package/package.json +147 -0
- package/skills/create-table-hook/SKILL.md +117 -0
- package/skills/getting-started/SKILL.md +160 -0
- package/skills/table-state/SKILL.md +187 -0
- package/src/FlexRender.gts +267 -0
- package/src/create-table-hook.ts +83 -0
- package/src/experimental-worker-plugin.ts +1 -0
- package/src/flex-render-helpers.ts +103 -0
- package/src/flex-render.ts +6 -0
- package/src/index.ts +31 -0
- package/src/reactivity.ts +39 -0
- package/src/signal.ts +126 -0
- package/src/static-functions.ts +1 -0
- 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'
|