@tanstack/devtools-utils 0.3.3 → 0.4.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 (58) hide show
  1. package/bin/intent.js +20 -0
  2. package/dist/preact/esm/index.js +1 -5
  3. package/dist/preact/esm/panel.d.ts +4 -5
  4. package/dist/preact/esm/panel.js +48 -28
  5. package/dist/preact/esm/panel.js.map +1 -1
  6. package/dist/preact/esm/plugin.d.ts +3 -2
  7. package/dist/preact/esm/plugin.js +20 -22
  8. package/dist/preact/esm/plugin.js.map +1 -1
  9. package/dist/react/esm/index.js +1 -5
  10. package/dist/react/esm/panel.d.ts +4 -4
  11. package/dist/react/esm/panel.js +47 -28
  12. package/dist/react/esm/panel.js.map +1 -1
  13. package/dist/react/esm/plugin.d.ts +3 -2
  14. package/dist/react/esm/plugin.js +20 -22
  15. package/dist/react/esm/plugin.js.map +1 -1
  16. package/dist/solid/esm/chunk/{ZXPCWXRH.js → 7TSS377A.js} +2 -10
  17. package/dist/solid/esm/chunk/{UX5ZZ4MG.js → WUD4VD3N.js} +2 -10
  18. package/dist/solid/esm/class-mount-impl/5TF6RAHJ.js +1 -0
  19. package/dist/solid/esm/class-mount-impl/VN5QTPB3.js +1 -0
  20. package/dist/solid/esm/dev.js +9 -11
  21. package/dist/solid/esm/index.d.ts +10 -10
  22. package/dist/solid/esm/index.js +9 -11
  23. package/dist/solid/esm/server.js +8 -10
  24. package/dist/solid-class/esm/class-mount-impl.d.ts +3 -2
  25. package/dist/solid-class/esm/class-mount-impl.js +17 -23
  26. package/dist/solid-class/esm/class-mount-impl.js.map +1 -1
  27. package/dist/solid-class/esm/class.d.ts +4 -3
  28. package/dist/solid-class/esm/class.js +57 -54
  29. package/dist/solid-class/esm/class.js.map +1 -1
  30. package/dist/solid-class/esm/panel.d.ts +3 -3
  31. package/dist/solid-class/esm/plugin.d.ts +3 -2
  32. package/dist/vue/esm/index.js +1 -5
  33. package/dist/vue/esm/panel.d.ts +3 -3
  34. package/dist/vue/esm/panel.js +36 -45
  35. package/dist/vue/esm/panel.js.map +1 -1
  36. package/dist/vue/esm/plugin.js +20 -19
  37. package/dist/vue/esm/plugin.js.map +1 -1
  38. package/package.json +12 -9
  39. package/skills/devtools-framework-adapters/SKILL.md +263 -0
  40. package/skills/devtools-framework-adapters/references/preact.md +262 -0
  41. package/skills/devtools-framework-adapters/references/react.md +241 -0
  42. package/skills/devtools-framework-adapters/references/solid.md +274 -0
  43. package/skills/devtools-framework-adapters/references/vue.md +270 -0
  44. package/src/preact/panel.tsx +6 -7
  45. package/src/preact/plugin.tsx +4 -3
  46. package/src/react/panel.tsx +6 -7
  47. package/src/react/plugin.tsx +4 -3
  48. package/src/solid/class-mount-impl.tsx +7 -10
  49. package/src/solid/class.test.tsx +43 -12
  50. package/src/solid/class.ts +15 -4
  51. package/src/solid/panel.tsx +6 -7
  52. package/src/solid/plugin.tsx +4 -3
  53. package/src/vue/panel.ts +6 -7
  54. package/dist/preact/esm/index.js.map +0 -1
  55. package/dist/react/esm/index.js.map +0 -1
  56. package/dist/solid/esm/class-mount-impl/LK7V47IP.js +0 -1
  57. package/dist/solid/esm/class-mount-impl/ZAIAXV4M.js +0 -1
  58. package/dist/vue/esm/index.js.map +0 -1
@@ -0,0 +1,241 @@
1
+ # React Framework Adapter Reference
2
+
3
+ ## Import
4
+
5
+ ```ts
6
+ import {
7
+ createReactPlugin,
8
+ createReactPanel,
9
+ } from '@tanstack/devtools-utils/react'
10
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/react'
11
+ ```
12
+
13
+ ## DevtoolsPanelProps
14
+
15
+ ```ts
16
+ interface DevtoolsPanelProps {
17
+ theme?: 'light' | 'dark'
18
+ }
19
+ ```
20
+
21
+ ## createReactPlugin
22
+
23
+ Creates a `[Plugin, NoOpPlugin]` tuple from a React component and plugin metadata.
24
+
25
+ ### Signature
26
+
27
+ ```ts
28
+ function createReactPlugin(options: {
29
+ name: string
30
+ id?: string
31
+ defaultOpen?: boolean
32
+ Component: (props: DevtoolsPanelProps) => JSX.Element
33
+ }): readonly [Plugin, NoOpPlugin]
34
+ ```
35
+
36
+ ### Parameters
37
+
38
+ | Parameter | Type | Required | Description |
39
+ | ------------- | -------------------------------------------- | -------- | -------------------------------------- |
40
+ | `name` | `string` | Yes | Display name shown in the devtools tab |
41
+ | `id` | `string` | No | Unique identifier for the plugin |
42
+ | `defaultOpen` | `boolean` | No | Whether the plugin panel starts open |
43
+ | `Component` | `(props: DevtoolsPanelProps) => JSX.Element` | Yes | React component to render in the panel |
44
+
45
+ ### Return Value
46
+
47
+ A `readonly [Plugin, NoOpPlugin]` tuple:
48
+
49
+ - **`Plugin()`** -- returns `{ name, id?, defaultOpen?, render(el: HTMLElement, theme: 'light' | 'dark') => JSX.Element }`. The `render` function renders `<Component theme={theme} />`.
50
+ - **`NoOpPlugin()`** -- returns the same shape but `render` returns `<></>`.
51
+
52
+ ### Source
53
+
54
+ ```tsx
55
+ // packages/devtools-utils/src/react/plugin.tsx
56
+ export function createReactPlugin({
57
+ Component,
58
+ ...config
59
+ }: {
60
+ name: string
61
+ id?: string
62
+ defaultOpen?: boolean
63
+ Component: (props: DevtoolsPanelProps) => JSX.Element
64
+ }) {
65
+ function Plugin() {
66
+ return {
67
+ ...config,
68
+ render: (_el: HTMLElement, theme: 'light' | 'dark') => (
69
+ <Component theme={theme} />
70
+ ),
71
+ }
72
+ }
73
+ function NoOpPlugin() {
74
+ return {
75
+ ...config,
76
+ render: (_el: HTMLElement, _theme: 'light' | 'dark') => <></>,
77
+ }
78
+ }
79
+ return [Plugin, NoOpPlugin] as const
80
+ }
81
+ ```
82
+
83
+ ### Usage
84
+
85
+ #### Basic
86
+
87
+ ```tsx
88
+ import { createReactPlugin } from '@tanstack/devtools-utils/react'
89
+
90
+ function MyStorePanel({ theme }: { theme?: 'light' | 'dark' }) {
91
+ return (
92
+ <div className={theme === 'dark' ? 'dark' : 'light'}>
93
+ <h2>My Store Devtools</h2>
94
+ </div>
95
+ )
96
+ }
97
+
98
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
99
+ name: 'My Store',
100
+ id: 'my-store',
101
+ defaultOpen: false,
102
+ Component: MyStorePanel,
103
+ })
104
+ ```
105
+
106
+ #### Inline Component
107
+
108
+ ```tsx
109
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
110
+ name: 'My Store',
111
+ Component: ({ theme }) => <MyStorePanel theme={theme} />,
112
+ })
113
+ ```
114
+
115
+ #### Production Tree-Shaking
116
+
117
+ ```tsx
118
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
119
+ name: 'My Store',
120
+ Component: MyStorePanel,
121
+ })
122
+
123
+ const ActivePlugin =
124
+ process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
125
+ ```
126
+
127
+ ## createReactPanel
128
+
129
+ Wraps a class-based devtools core (with `mount` and `unmount` methods) in a React component that handles lifecycle.
130
+
131
+ ### Signature
132
+
133
+ ```ts
134
+ function createReactPanel<
135
+ TComponentProps extends DevtoolsPanelProps | undefined,
136
+ TCoreDevtoolsClass extends {
137
+ mount: (el: HTMLElement, theme: 'light' | 'dark') => void
138
+ unmount: () => void
139
+ },
140
+ >(CoreClass: new () => TCoreDevtoolsClass): readonly [Panel, NoOpPanel]
141
+ ```
142
+
143
+ ### Parameters
144
+
145
+ | Parameter | Type | Required | Description |
146
+ | ----------- | ------------------------------------------------------- | -------- | --------------------------------------- |
147
+ | `CoreClass` | `new () => { mount(el, theme): void; unmount(): void }` | Yes | Class constructor for the devtools core |
148
+
149
+ ### Return Value
150
+
151
+ A `readonly [Panel, NoOpPanel]` tuple:
152
+
153
+ - **`Panel`** -- A React component that:
154
+ - Creates a `<div style={{ height: '100%' }}>` with a ref.
155
+ - Instantiates `CoreClass` on mount via `useEffect`.
156
+ - Calls `core.mount(el, props.theme ?? 'dark')`.
157
+ - Calls `core.unmount()` on cleanup.
158
+ - Re-runs the effect when `theme` prop changes.
159
+ - **`NoOpPanel`** -- Renders `<></>`.
160
+
161
+ ### Source
162
+
163
+ ```tsx
164
+ // packages/devtools-utils/src/react/panel.tsx
165
+ export function createReactPanel<
166
+ TComponentProps extends DevtoolsPanelProps | undefined,
167
+ TCoreDevtoolsClass extends {
168
+ mount: (el: HTMLElement, theme: 'light' | 'dark') => void
169
+ unmount: () => void
170
+ },
171
+ >(CoreClass: new () => TCoreDevtoolsClass) {
172
+ function Panel(props: TComponentProps) {
173
+ const devToolRef = useRef<HTMLDivElement>(null)
174
+ const devtools = useRef<TCoreDevtoolsClass | null>(null)
175
+ useEffect(() => {
176
+ if (devtools.current) return
177
+ devtools.current = new CoreClass()
178
+ if (devToolRef.current) {
179
+ devtools.current.mount(devToolRef.current, props?.theme ?? 'dark')
180
+ }
181
+ return () => {
182
+ if (devToolRef.current) {
183
+ devtools.current?.unmount()
184
+ devtools.current = null
185
+ }
186
+ }
187
+ }, [props?.theme])
188
+ return <div style={{ height: '100%' }} ref={devToolRef} />
189
+ }
190
+
191
+ function NoOpPanel(_props: TComponentProps) {
192
+ return <></>
193
+ }
194
+ return [Panel, NoOpPanel] as const
195
+ }
196
+ ```
197
+
198
+ ### Usage
199
+
200
+ #### Composing Panel + Plugin
201
+
202
+ ```tsx
203
+ import {
204
+ createReactPanel,
205
+ createReactPlugin,
206
+ } from '@tanstack/devtools-utils/react'
207
+
208
+ class MyDevtoolsCore {
209
+ mount(el: HTMLElement, theme: 'light' | 'dark') {
210
+ // Use DOM APIs to render your devtools UI into the provided element
211
+ const container = document.createElement('div')
212
+ container.className = theme
213
+ container.textContent = 'Devtools loaded'
214
+ el.appendChild(container)
215
+ }
216
+ unmount() {
217
+ // Clean up event listeners, subscriptions, etc.
218
+ }
219
+ }
220
+
221
+ // Step 1: Create the panel component from the class
222
+ const [MyPanel, NoOpPanel] = createReactPanel(MyDevtoolsCore)
223
+
224
+ // Step 2: Create the plugin from the panel component
225
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
226
+ name: 'My Store',
227
+ Component: MyPanel,
228
+ })
229
+
230
+ // Step 3: Use conditionally for production
231
+ const ActivePlugin =
232
+ process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
233
+ ```
234
+
235
+ ## React-Specific Gotchas
236
+
237
+ 1. **`useEffect` dependency on `theme`**: The panel re-runs the mount effect when `theme` changes. This means the core class is unmounted and re-mounted on theme change. Design your core class to handle this gracefully.
238
+
239
+ 2. **Ref guard**: `createReactPanel` uses `if (devtools.current) return` to prevent double-mounting in React Strict Mode. Do not remove this guard.
240
+
241
+ 3. **Default theme is `'dark'`**: If `props.theme` is undefined, the panel defaults to `'dark'`.
@@ -0,0 +1,274 @@
1
+ # Solid Framework Adapter Reference
2
+
3
+ ## Import
4
+
5
+ ```ts
6
+ import {
7
+ createSolidPlugin,
8
+ createSolidPanel,
9
+ } from '@tanstack/devtools-utils/solid'
10
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid'
11
+
12
+ // For class-based lazy loading (separate subpath)
13
+ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class'
14
+ ```
15
+
16
+ ## DevtoolsPanelProps
17
+
18
+ ```ts
19
+ interface DevtoolsPanelProps {
20
+ theme?: 'light' | 'dark'
21
+ }
22
+ ```
23
+
24
+ ## createSolidPlugin
25
+
26
+ Creates a `[Plugin, NoOpPlugin]` tuple from a Solid component and plugin metadata. Same options-object API as React.
27
+
28
+ ### Signature
29
+
30
+ ```ts
31
+ function createSolidPlugin(options: {
32
+ name: string
33
+ id?: string
34
+ defaultOpen?: boolean
35
+ Component: (props: DevtoolsPanelProps) => JSX.Element
36
+ }): readonly [Plugin, NoOpPlugin]
37
+ ```
38
+
39
+ ### Parameters
40
+
41
+ | Parameter | Type | Required | Description |
42
+ | ------------- | -------------------------------------------- | -------- | -------------------------------------- |
43
+ | `name` | `string` | Yes | Display name shown in the devtools tab |
44
+ | `id` | `string` | No | Unique identifier for the plugin |
45
+ | `defaultOpen` | `boolean` | No | Whether the plugin panel starts open |
46
+ | `Component` | `(props: DevtoolsPanelProps) => JSX.Element` | Yes | Solid component function |
47
+
48
+ ### Return Value
49
+
50
+ A `readonly [Plugin, NoOpPlugin]` tuple:
51
+
52
+ - **`Plugin()`** -- returns `{ name, id?, defaultOpen?, render(el, theme) }`. The `render` function returns `<Component theme={theme} />`.
53
+ - **`NoOpPlugin()`** -- returns the same shape but `render` returns `<></>`.
54
+
55
+ ### Source
56
+
57
+ ```tsx
58
+ // packages/devtools-utils/src/solid/plugin.tsx
59
+ /** @jsxImportSource solid-js */
60
+ import type { JSX } from 'solid-js'
61
+ import type { DevtoolsPanelProps } from './panel'
62
+
63
+ export function createSolidPlugin({
64
+ Component,
65
+ ...config
66
+ }: {
67
+ name: string
68
+ id?: string
69
+ defaultOpen?: boolean
70
+ Component: (props: DevtoolsPanelProps) => JSX.Element
71
+ }) {
72
+ function Plugin() {
73
+ return {
74
+ ...config,
75
+ render: (_el: HTMLElement, theme: 'light' | 'dark') => {
76
+ return <Component theme={theme} />
77
+ },
78
+ }
79
+ }
80
+ function NoOpPlugin() {
81
+ return {
82
+ ...config,
83
+ render: (_el: HTMLElement, _theme: 'light' | 'dark') => <></>,
84
+ }
85
+ }
86
+ return [Plugin, NoOpPlugin] as const
87
+ }
88
+ ```
89
+
90
+ ### Usage
91
+
92
+ #### Basic
93
+
94
+ ```tsx
95
+ import { createSolidPlugin } from '@tanstack/devtools-utils/solid'
96
+
97
+ function MyStorePanel(props: { theme?: 'light' | 'dark' }) {
98
+ return (
99
+ <div class={props.theme === 'dark' ? 'dark' : 'light'}>
100
+ <h2>My Store Devtools</h2>
101
+ </div>
102
+ )
103
+ }
104
+
105
+ const [MyPlugin, NoOpPlugin] = createSolidPlugin({
106
+ name: 'My Store',
107
+ id: 'my-store',
108
+ defaultOpen: false,
109
+ Component: MyStorePanel,
110
+ })
111
+ ```
112
+
113
+ #### Inline Component
114
+
115
+ ```tsx
116
+ const [MyPlugin, NoOpPlugin] = createSolidPlugin({
117
+ name: 'My Store',
118
+ Component: (props) => <MyStorePanel theme={props.theme} />,
119
+ })
120
+ ```
121
+
122
+ #### Production Tree-Shaking
123
+
124
+ ```tsx
125
+ const [MyPlugin, NoOpPlugin] = createSolidPlugin({
126
+ name: 'My Store',
127
+ Component: MyStorePanel,
128
+ })
129
+
130
+ const ActivePlugin = import.meta.env.DEV ? MyPlugin : NoOpPlugin
131
+ ```
132
+
133
+ ## createSolidPanel
134
+
135
+ Wraps a class-based devtools core in a Solid component.
136
+
137
+ ### Signature
138
+
139
+ ```ts
140
+ function createSolidPanel<
141
+ TComponentProps extends DevtoolsPanelProps | undefined,
142
+ >(CoreClass: ClassType): readonly [Panel, NoOpPanel]
143
+ ```
144
+
145
+ Where `ClassType` is `ReturnType<typeof constructCoreClass>[0]` -- a class with `mount(el, theme)` and `unmount()`.
146
+
147
+ ### Return Value
148
+
149
+ - **`Panel`** -- A Solid component that:
150
+ - Creates a `<div style={{ height: '100%' }}>` with a ref.
151
+ - Instantiates `CoreClass` immediately via `createSignal`.
152
+ - Calls `core.mount(el, props.theme ?? 'dark')` inside `onMount`.
153
+ - Calls `core.unmount()` via `onCleanup` (nested inside `onMount`).
154
+ - **`NoOpPanel`** -- Renders `<></>`.
155
+
156
+ ### Source
157
+
158
+ ```tsx
159
+ // packages/devtools-utils/src/solid/panel.tsx
160
+ /** @jsxImportSource solid-js */
161
+ import { createSignal, onCleanup, onMount } from 'solid-js'
162
+ import type { ClassType } from './class'
163
+
164
+ export function createSolidPanel<
165
+ TComponentProps extends DevtoolsPanelProps | undefined,
166
+ >(CoreClass: ClassType) {
167
+ function Panel(props: TComponentProps) {
168
+ let devToolRef: HTMLDivElement | undefined
169
+ const [devtools] = createSignal(new CoreClass())
170
+ onMount(() => {
171
+ if (devToolRef) {
172
+ devtools().mount(devToolRef, props?.theme ?? 'dark')
173
+ }
174
+ onCleanup(() => {
175
+ devtools().unmount()
176
+ })
177
+ })
178
+ return <div style={{ height: '100%' }} ref={devToolRef} />
179
+ }
180
+
181
+ function NoOpPanel(_props: TComponentProps) {
182
+ return <></>
183
+ }
184
+
185
+ return [Panel, NoOpPanel] as const
186
+ }
187
+ ```
188
+
189
+ ### Usage
190
+
191
+ ```tsx
192
+ import {
193
+ createSolidPanel,
194
+ createSolidPlugin,
195
+ } from '@tanstack/devtools-utils/solid'
196
+ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class'
197
+
198
+ // Step 1: Build a core class with lazy loading
199
+ const [MyDevtoolsCore, NoOpCore] = constructCoreClass(
200
+ () => import('./MyDevtoolsUI'),
201
+ )
202
+
203
+ // Step 2: Create panel from core class
204
+ const [MyPanel, NoOpPanel] = createSolidPanel(MyDevtoolsCore)
205
+
206
+ // Step 3: Create plugin from panel
207
+ const [MyPlugin, NoOpPlugin] = createSolidPlugin({
208
+ name: 'My Store',
209
+ Component: MyPanel,
210
+ })
211
+ ```
212
+
213
+ ## constructCoreClass
214
+
215
+ Solid has an additional utility for building lazy-loaded devtools cores. Import from the separate subpath `@tanstack/devtools-utils/solid/class`.
216
+
217
+ ### Signature
218
+
219
+ ```ts
220
+ function constructCoreClass(
221
+ importFn: () => Promise<{ default: () => JSX.Element }>,
222
+ ): readonly [DevtoolsCore, NoOpDevtoolsCore]
223
+ ```
224
+
225
+ ### Behavior
226
+
227
+ - **`DevtoolsCore`** -- Has an async `mount(el, theme)` that dynamically imports the component, then mounts it into `el`. Tracks mounting state to prevent double-mounting. Supports abort if `unmount()` is called during the async import.
228
+ - **`NoOpDevtoolsCore`** -- Extends `DevtoolsCore` but `mount` and `unmount` are no-ops.
229
+
230
+ ### Usage
231
+
232
+ ```ts
233
+ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class'
234
+
235
+ const [DevtoolsCore, NoOpDevtoolsCore] = constructCoreClass(
236
+ () => import('./MyDevtoolsPanel'),
237
+ )
238
+
239
+ // Use DevtoolsCore with createSolidPanel
240
+ // Use NoOpDevtoolsCore for production
241
+ ```
242
+
243
+ ## Solid-Specific Gotchas
244
+
245
+ 1. **Component must be a function, not JSX.** The `Component` field expects a function `(props) => JSX.Element`, not evaluated JSX.
246
+
247
+ ```tsx
248
+ // WRONG -- <MyPanel /> is JSX.Element, not a component function
249
+ Component: <MyPanel />
250
+
251
+ // CORRECT -- pass the component function
252
+ Component: MyStorePanel
253
+
254
+ // ALSO CORRECT -- wrap in arrow function
255
+ Component: (props) => <MyStorePanel theme={props.theme} />
256
+ ```
257
+
258
+ 2. **Solid props are accessed via `props.theme`, not destructured.** Solid's reactivity requires accessing props through the props object. Destructuring breaks reactivity.
259
+
260
+ ```tsx
261
+ // CAUTION -- destructuring may break reactivity tracking
262
+ const Component = ({ theme }: DevtoolsPanelProps) => <div>{theme}</div>
263
+
264
+ // PREFERRED -- access via props object
265
+ const Component = (props: DevtoolsPanelProps) => <div>{props.theme}</div>
266
+ ```
267
+
268
+ 3. **Default theme is `'dark'`.** If `props.theme` is undefined, the panel defaults to `'dark'`.
269
+
270
+ 4. **`onCleanup` is nested inside `onMount`.** In `createSolidPanel`, cleanup is registered inside `onMount`, which is the Solid idiom for pairing mount/unmount lifecycle.
271
+
272
+ 5. **Core class instantiation is eager.** `createSignal(new CoreClass())` runs immediately when the Panel component is created, not lazily. The actual `mount` call happens in `onMount`.
273
+
274
+ 6. **`constructCoreClass` handles async import abort.** If `unmount()` is called while the dynamic import is still in flight, the mount is aborted cleanly. No need to handle this manually.