@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,103 @@
1
+ import type { ComponentLike } from '@glint/template'
2
+ import type {
3
+ CellContext,
4
+ CellData,
5
+ HeaderContext,
6
+ RowData,
7
+ TableFeatures,
8
+ } from '@tanstack/table-core'
9
+ export { flexRender } from '@tanstack/table-core/flex-render'
10
+
11
+ export type FlexRenderContext<
12
+ TFeatures extends TableFeatures = TableFeatures,
13
+ TData extends RowData = RowData,
14
+ TValue extends CellData = CellData,
15
+ > =
16
+ | CellContext<TFeatures, TData, TValue>
17
+ | HeaderContext<TFeatures, TData, TValue>
18
+
19
+ export interface FlexRenderableSignature<
20
+ TFeatures extends TableFeatures = TableFeatures,
21
+ TData extends RowData = RowData,
22
+ TValue extends CellData = CellData,
23
+ TOptions = undefined,
24
+ > {
25
+ Args: {
26
+ ctx: FlexRenderContext<TFeatures, TData, TValue>
27
+ options?: TOptions
28
+ }
29
+ }
30
+
31
+ export interface CellRenderableSignature<
32
+ TFeatures extends TableFeatures = TableFeatures,
33
+ TData extends RowData = RowData,
34
+ TValue extends CellData = CellData,
35
+ TOptions = undefined,
36
+ > {
37
+ Args: {
38
+ ctx: CellContext<TFeatures, TData, TValue>
39
+ options?: TOptions
40
+ }
41
+ }
42
+
43
+ type FlexRenderableComponent<
44
+ TFeatures extends TableFeatures,
45
+ TData extends RowData,
46
+ TValue extends CellData,
47
+ TOptions,
48
+ > =
49
+ | ComponentLike<FlexRenderableSignature<TFeatures, TData, TValue, TOptions>>
50
+ | ComponentLike<CellRenderableSignature<TFeatures, TData, TValue, TOptions>>
51
+
52
+ export class FlexRenderComponentConfig<
53
+ TFeatures extends TableFeatures = TableFeatures,
54
+ TData extends RowData = RowData,
55
+ TValue extends CellData = CellData,
56
+ TOptions = undefined,
57
+ > {
58
+ readonly component: FlexRenderableComponent<
59
+ TFeatures,
60
+ TData,
61
+ TValue,
62
+ TOptions
63
+ >
64
+ readonly options?: TOptions
65
+
66
+ constructor(
67
+ component: FlexRenderableComponent<TFeatures, TData, TValue, TOptions>,
68
+ options?: TOptions,
69
+ ) {
70
+ this.component = component
71
+ this.options = options
72
+ }
73
+ }
74
+
75
+ export function flexRenderComponent<
76
+ TFeatures extends TableFeatures,
77
+ TData extends RowData,
78
+ TValue extends CellData,
79
+ >(
80
+ component: FlexRenderableComponent<TFeatures, TData, TValue, undefined>,
81
+ ): FlexRenderComponentConfig<TFeatures, TData, TValue, undefined>
82
+
83
+ export function flexRenderComponent<
84
+ TFeatures extends TableFeatures,
85
+ TData extends RowData,
86
+ TValue extends CellData,
87
+ TOptions,
88
+ >(
89
+ component: FlexRenderableComponent<TFeatures, TData, TValue, TOptions>,
90
+ options: TOptions,
91
+ ): FlexRenderComponentConfig<TFeatures, TData, TValue, TOptions>
92
+
93
+ export function flexRenderComponent<
94
+ TFeatures extends TableFeatures = TableFeatures,
95
+ TData extends RowData = RowData,
96
+ TValue extends CellData = CellData,
97
+ TOptions = undefined,
98
+ >(
99
+ component: FlexRenderableComponent<TFeatures, TData, TValue, TOptions>,
100
+ options?: TOptions,
101
+ ): FlexRenderComponentConfig<TFeatures, TData, TValue, TOptions> {
102
+ return new FlexRenderComponentConfig(component, options)
103
+ }
@@ -0,0 +1,6 @@
1
+ export * from './flex-render-helpers.ts'
2
+ export {
3
+ FlexRenderCell,
4
+ FlexRenderHeader,
5
+ FlexRenderFooter,
6
+ } from './FlexRender.gts'
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ export * from '@tanstack/table-core'
2
+ export { useTable } from './use-table.ts'
3
+ export { createTableHook } from './create-table-hook.ts'
4
+ export type {
5
+ CreateTableHookOptions,
6
+ AppEmberTable,
7
+ AppColumnHelper,
8
+ } from './create-table-hook.ts'
9
+ export {
10
+ flexRender,
11
+ flexRenderComponent,
12
+ FlexRenderComponentConfig,
13
+ } from './flex-render.ts'
14
+ export type {
15
+ FlexRenderableSignature,
16
+ CellRenderableSignature,
17
+ FlexRenderContext,
18
+ } from './flex-render.ts'
19
+ export {
20
+ FlexRenderCell,
21
+ FlexRenderHeader,
22
+ FlexRenderFooter,
23
+ } from './FlexRender.gts'
24
+ export { emberReactivity } from './reactivity.ts'
25
+ export {
26
+ computed,
27
+ signal,
28
+ createAtom,
29
+ Signal,
30
+ ComputedSignal,
31
+ } from './signal.ts'
@@ -0,0 +1,39 @@
1
+ import type { Subscription } from '@tanstack/store'
2
+ import type {
3
+ TableAtomOptions,
4
+ TableReactivityBindings,
5
+ } from '@tanstack/table-core/reactivity'
6
+
7
+ import { untrack } from '@glimmer/validator'
8
+ import { computed, signal } from './signal.ts'
9
+
10
+ export function emberReactivity(): TableReactivityBindings {
11
+ const subscriptions = new Set<Subscription>()
12
+
13
+ return {
14
+ createOptionsStore: true,
15
+ wrapExternalAtoms: true,
16
+
17
+ // timing is not important, but the main thing is that the work does *not*
18
+ // happen during the render phase.
19
+ schedule: (fn) => queueMicrotask(() => fn()),
20
+ batch: (fn) => fn(),
21
+ untrack,
22
+ // @cached
23
+ createReadonlyAtom: <T>(fn: () => T) => {
24
+ return computed(fn)
25
+ },
26
+ // @tracked
27
+ createWritableAtom: <T>(value: T, options?: TableAtomOptions<T>) => {
28
+ return signal(value, options)
29
+ },
30
+ // Not for the ember integration, but for the tanstack inspector
31
+ addSubscription: (subscription) => {
32
+ subscriptions.add(subscription)
33
+ },
34
+ unmount: () => {
35
+ subscriptions.forEach((s) => s.unsubscribe())
36
+ subscriptions.clear()
37
+ },
38
+ }
39
+ }
package/src/signal.ts ADDED
@@ -0,0 +1,126 @@
1
+ import type { Atom, AtomOptions, Observer, Subscription } from '@tanstack/store'
2
+
3
+ import { tracked, cached } from '@glimmer/tracking'
4
+ import { untrack } from '@glimmer/validator'
5
+
6
+ export function subscribeNoEffect(): Subscription {
7
+ return null as unknown as Subscription
8
+ }
9
+
10
+ /**
11
+ * Ember-native signal implementation.
12
+ * In future ember >7.3 `tracked` will be available by itself
13
+ * so the class wrapper will not be needed for those versions
14
+ */
15
+ export class Signal<T> {
16
+ @tracked _value
17
+
18
+ #options: AtomOptions<T> | undefined
19
+
20
+ // Ember reads are tag-tracked; this observer list exists for the plain-JS
21
+ // subscribers core wires up (external-atom sync in constructTable, the
22
+ // inspector), which have no access to framework tracking.
23
+ #listeners = new Set<(value: T) => void>()
24
+
25
+ constructor(value: T, options: AtomOptions<T> | undefined) {
26
+ this._value = value
27
+ this.#options = options
28
+ }
29
+
30
+ subscribe(
31
+ listenerOrObserver: Observer<T> | ((value: T) => void),
32
+ ): Subscription {
33
+ const listener =
34
+ typeof listenerOrObserver === 'function'
35
+ ? listenerOrObserver
36
+ : listenerOrObserver.next
37
+
38
+ if (!listener) {
39
+ return { unsubscribe: () => {} }
40
+ }
41
+
42
+ this.#listeners.add(listener)
43
+ return { unsubscribe: () => this.#listeners.delete(listener) }
44
+ }
45
+
46
+ get() {
47
+ return this.value
48
+ }
49
+ set(value: T | ((prev: T) => T)) {
50
+ if (typeof value === 'function') {
51
+ return this.update(value as unknown as (prev: T) => T)
52
+ }
53
+
54
+ this.value = value
55
+ }
56
+
57
+ get value() {
58
+ return this._value
59
+ }
60
+
61
+ set value(next: T) {
62
+ const prev = untrack(() => this._value)
63
+
64
+ const isEqual = this.#options?.compare
65
+ ? this.#options.compare(prev, next)
66
+ : prev === next
67
+ if (isEqual) {
68
+ return
69
+ }
70
+
71
+ this._value = next
72
+
73
+ for (const listener of this.#listeners) {
74
+ listener(next)
75
+ }
76
+ }
77
+
78
+ update(fn: (value: T) => T) {
79
+ const original = untrack(() => this._value)
80
+
81
+ this.value = fn(original)
82
+ }
83
+ }
84
+
85
+ export class ComputedSignal<T> {
86
+ #compute
87
+
88
+ constructor(compute: () => T) {
89
+ this.#compute = compute
90
+ }
91
+
92
+ get() {
93
+ return this.value
94
+ }
95
+
96
+ subscribe = subscribeNoEffect
97
+
98
+ @cached
99
+ get value() {
100
+ return this.#compute()
101
+ }
102
+ }
103
+
104
+ export function signal<T>(value: T, options: AtomOptions<T> | undefined) {
105
+ return new Signal(value, options)
106
+ }
107
+
108
+ /**
109
+ * Creates an Ember-native writable atom, satisfying the `@tanstack/store`
110
+ * `Atom` contract so it can be passed to `options.atoms`. Because it is backed
111
+ * by a `@tracked` Signal, reading `atom.get()` directly in a template or
112
+ * getter is reactive — unlike a foreign `@tanstack/store` atom, whose reads
113
+ * create no Glimmer tag dependency.
114
+ *
115
+ * Takes a plain initial value only; there is no derived/function overload.
116
+ */
117
+ export function createAtom<T>(
118
+ initialValue: T,
119
+ options?: AtomOptions<T>,
120
+ ): Atom<T> {
121
+ return signal(initialValue, options)
122
+ }
123
+
124
+ export function computed<T>(fn: () => T) {
125
+ return new ComputedSignal(fn)
126
+ }
@@ -0,0 +1 @@
1
+ export * from '@tanstack/table-core/static-functions'
@@ -0,0 +1,154 @@
1
+ import { constructTable } from '@tanstack/table-core'
2
+ import { untrack } from '@glimmer/validator'
3
+ import { emberReactivity } from './reactivity.ts'
4
+ import { computed, subscribeNoEffect } from './signal.ts'
5
+ import type {
6
+ RowData,
7
+ Table,
8
+ TableFeatures,
9
+ TableOptions,
10
+ TableState,
11
+ } from '@tanstack/table-core'
12
+ import type { Atom, ReadonlyAtom, ReadonlyStore } from '@tanstack/store'
13
+
14
+ // Internal table slots used by the pull-based options/state wiring below.
15
+ // `Table_Internal` is not exported from the table-core build, so the shape is
16
+ // declared structurally here.
17
+ interface TableInternals<
18
+ TFeatures extends TableFeatures,
19
+ TData extends RowData,
20
+ > {
21
+ optionsStore?: {
22
+ get(): TableOptions<TFeatures, TData>
23
+ set(value: () => TableOptions<TFeatures, TData>): void
24
+ }
25
+ baseAtoms: Record<string, { get(): unknown }>
26
+ atoms: Record<string, unknown>
27
+ }
28
+
29
+ export function useTable<
30
+ TFeatures extends TableFeatures,
31
+ TData extends RowData,
32
+ >(getOptions: () => TableOptions<TFeatures, TData>): Table<TFeatures, TData> {
33
+ const reactivity = emberReactivity()
34
+
35
+ // Creates reactive read only signal for options
36
+ const userOptions = computed(getOptions)
37
+
38
+ // Untracked to prevent possible "set on same computation as read" errors in Ember.
39
+ const initialOptions = untrack(() => userOptions.get())
40
+
41
+ const table = constructTable<TFeatures, TData>({
42
+ ...initialOptions,
43
+ features: {
44
+ coreReactivityFeature: reactivity,
45
+ ...initialOptions.features,
46
+ },
47
+ mergeOptions: (
48
+ defaultOptions: TableOptions<TFeatures, TData>,
49
+ newOptions: Partial<TableOptions<TFeatures, TData>>,
50
+ ) => ({
51
+ ...defaultOptions,
52
+ ...newOptions,
53
+ }),
54
+ }) as Table<TFeatures, TData> & TableInternals<TFeatures, TData>
55
+
56
+ const optionsStore = table.optionsStore!
57
+
58
+ const liveOptions = computed(() => {
59
+ const stored = optionsStore.get()
60
+ return {
61
+ ...stored,
62
+ ...userOptions.get(),
63
+ // stored options carry construct-time normalization (the reactivity
64
+ // feature, wrapped external atoms) that must win over the raw user
65
+ // options.
66
+ features: stored.features,
67
+ atoms: stored.atoms,
68
+ }
69
+ })
70
+
71
+ /**
72
+ * This is to get around core table not using lazy access so we need to re-wrap
73
+ *
74
+ * Similar to other reactive signal frameworks (solid, angular, svelte)
75
+ */
76
+ Object.defineProperty(table, 'options', {
77
+ configurable: true,
78
+ enumerable: true,
79
+ get: () => liveOptions.get(),
80
+ set: (value: TableOptions<TFeatures, TData>) => {
81
+ optionsStore.set(() => value)
82
+ },
83
+ })
84
+
85
+ const atoms: Record<string, ReadonlyAtom<unknown>> = table.atoms
86
+ const stateKeys = Object.keys(table.baseAtoms)
87
+
88
+ for (const key of stateKeys) {
89
+ const baseAtom = (table.baseAtoms as Record<string, ReadonlyAtom<unknown>>)[
90
+ key
91
+ ]!
92
+
93
+ /**
94
+ * Original atoms could cause effects for top level properties
95
+ *
96
+ * Core table should migrate to pure derived data which would boost render
97
+ * performance for both signal and non-signal frameworks.
98
+ */
99
+ atoms[key] = reactivity.createReadonlyAtom(
100
+ () => {
101
+ const externalAtom = (
102
+ table.options.atoms as Record<string, Atom<unknown>> | undefined
103
+ )?.[key]
104
+ if (externalAtom) {
105
+ return externalAtom.get()
106
+ }
107
+ const stateSlice = (
108
+ table.options.state as Record<string, unknown> | undefined
109
+ )?.[key]
110
+ if (stateSlice !== undefined) {
111
+ return stateSlice
112
+ }
113
+ return baseAtom.get()
114
+ },
115
+ { debugName: `table/atoms/${key}` },
116
+ )
117
+ }
118
+
119
+ const stateProxy: TableState<TFeatures> = new Proxy(
120
+ {},
121
+ {
122
+ get: (_target, key) =>
123
+ typeof key === 'string' ? atoms[key]?.get() : undefined,
124
+ has: (_target, key) => typeof key === 'string' && stateKeys.includes(key),
125
+ ownKeys: () => stateKeys,
126
+ getOwnPropertyDescriptor: (_target, key) =>
127
+ typeof key === 'string' && stateKeys.includes(key)
128
+ ? {
129
+ enumerable: true,
130
+ configurable: true,
131
+ value: atoms[key]!.get(),
132
+ }
133
+ : undefined,
134
+ },
135
+ ) as TableState<TFeatures>
136
+
137
+ /**
138
+ * Store is reasigned to point to proxy object to allow individual state slices to be independently reactive.
139
+ *
140
+ * Type cast is needed because we are setting during construction
141
+ * Table store is readonly after first initialization.
142
+ */
143
+ ;(
144
+ table as { store: Omit<ReadonlyStore<TableState<TFeatures>>, 'atom'> }
145
+ ).store = {
146
+ get: () => stateProxy,
147
+ get state() {
148
+ return stateProxy
149
+ },
150
+ subscribe: subscribeNoEffect,
151
+ }
152
+
153
+ return table
154
+ }