@tanstack/devtools-utils 0.3.2 → 0.3.4

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/bin/intent.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // Auto-generated by @tanstack/intent setup
3
+ // Exposes the intent end-user CLI for consumers of this library.
4
+ // Commit this file, then add to your package.json:
5
+ // "bin": { "intent": "./bin/intent.js" }
6
+ try {
7
+ await import('@tanstack/intent/intent-library')
8
+ } catch (e) {
9
+ if (e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND') {
10
+ console.error('@tanstack/intent is not installed.')
11
+ console.error('')
12
+ console.error('Install it as a dev dependency:')
13
+ console.error(' npm add -D @tanstack/intent')
14
+ console.error('')
15
+ console.error('Or run directly:')
16
+ console.error(' npx @tanstack/intent@latest list')
17
+ process.exit(1)
18
+ }
19
+ throw e
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/devtools-utils",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "TanStack Devtools utilities for creating your own devtools.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -65,6 +65,9 @@
65
65
  },
66
66
  "./package.json": "./package.json"
67
67
  },
68
+ "bin": {
69
+ "intent": "./bin/intent.js"
70
+ },
68
71
  "sideEffects": false,
69
72
  "engines": {
70
73
  "node": ">=18"
@@ -98,7 +101,9 @@
98
101
  },
99
102
  "files": [
100
103
  "dist/",
101
- "src"
104
+ "src",
105
+ "skills",
106
+ "bin"
102
107
  ],
103
108
  "devDependencies": {
104
109
  "tsup": "^8.5.0",
@@ -0,0 +1,263 @@
1
+ ---
2
+ name: devtools-framework-adapters
3
+ description: >
4
+ Use devtools-utils factory functions to create per-framework plugin adapters.
5
+ createReactPlugin/createSolidPlugin/createVuePlugin/createPreactPlugin,
6
+ createReactPanel/createSolidPanel/createVuePanel/createPreactPanel.
7
+ [Plugin, NoOpPlugin] tuple for tree-shaking. DevtoolsPanelProps (theme).
8
+ Vue uses (name, component) not options object. Solid render must be function.
9
+ type: framework
10
+ library: tanstack-devtools
11
+ library_version: '0.10.12'
12
+ requires:
13
+ - tanstack-devtools/plugin-panel
14
+ sources:
15
+ - 'TanStack/devtools:docs/devtools-utils.md'
16
+ - 'TanStack/devtools:packages/devtools-utils/src/react/plugin.tsx'
17
+ - 'TanStack/devtools:packages/devtools-utils/src/vue/plugin.ts'
18
+ - 'TanStack/devtools:packages/devtools-utils/src/solid/plugin.tsx'
19
+ - 'TanStack/devtools:packages/devtools-utils/src/preact/plugin.tsx'
20
+ ---
21
+
22
+ Use `@tanstack/devtools-utils` factory functions to create per-framework devtools plugin adapters. Each framework has a subpath export (`/react`, `/vue`, `/solid`, `/preact`) with two factories:
23
+
24
+ 1. **`createXPlugin`** -- wraps a component into a `[Plugin, NoOpPlugin]` tuple for tree-shaking.
25
+ 2. **`createXPanel`** -- wraps a class-based devtools core (`mount`/`unmount`) into a `[Panel, NoOpPanel]` component tuple.
26
+
27
+ ## Key Source Files
28
+
29
+ - `packages/devtools-utils/src/react/plugin.tsx` -- createReactPlugin
30
+ - `packages/devtools-utils/src/react/panel.tsx` -- createReactPanel, DevtoolsPanelProps
31
+ - `packages/devtools-utils/src/vue/plugin.ts` -- createVuePlugin (different API)
32
+ - `packages/devtools-utils/src/vue/panel.ts` -- createVuePanel, DevtoolsPanelProps (includes 'system' theme)
33
+ - `packages/devtools-utils/src/solid/plugin.tsx` -- createSolidPlugin
34
+ - `packages/devtools-utils/src/solid/panel.tsx` -- createSolidPanel
35
+ - `packages/devtools-utils/src/solid/class.ts` -- constructCoreClass (Solid-specific)
36
+ - `packages/devtools-utils/src/preact/plugin.tsx` -- createPreactPlugin
37
+ - `packages/devtools-utils/src/preact/panel.tsx` -- createPreactPanel
38
+
39
+ ## Shared Pattern
40
+
41
+ All four frameworks follow the same two-factory pattern:
42
+
43
+ ### Plugin Factory
44
+
45
+ Every `createXPlugin` returns `readonly [Plugin, NoOpPlugin]`:
46
+
47
+ - **Plugin** -- returns a plugin object with metadata and a `render` function that renders your component.
48
+ - **NoOpPlugin** -- returns a plugin object with the same metadata but renders an empty fragment.
49
+
50
+ ### Panel Factory
51
+
52
+ Every `createXPanel` returns `readonly [Panel, NoOpPanel]`:
53
+
54
+ - **Panel** -- a framework component that creates a `<div style="height:100%">`, instantiates the core class, calls `core.mount(el, theme)` on mount, and `core.unmount()` on cleanup.
55
+ - **NoOpPanel** -- renders an empty fragment.
56
+
57
+ ### DevtoolsPanelProps
58
+
59
+ ```ts
60
+ // React, Solid, Preact
61
+ interface DevtoolsPanelProps {
62
+ theme?: 'light' | 'dark'
63
+ }
64
+
65
+ // Vue (note: includes 'system')
66
+ interface DevtoolsPanelProps {
67
+ theme?: 'dark' | 'light' | 'system'
68
+ }
69
+ ```
70
+
71
+ Import from the framework subpath:
72
+
73
+ ```ts
74
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/react'
75
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
76
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid'
77
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/preact'
78
+ ```
79
+
80
+ ## Primary Example (React)
81
+
82
+ ```tsx
83
+ import { createReactPlugin } from '@tanstack/devtools-utils/react'
84
+
85
+ function MyStorePanel({ theme }: { theme?: 'light' | 'dark' }) {
86
+ return <div className={theme}>My Store Devtools</div>
87
+ }
88
+
89
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
90
+ name: 'My Store',
91
+ id: 'my-store',
92
+ defaultOpen: false,
93
+ Component: MyStorePanel,
94
+ })
95
+
96
+ // Tree-shaking: use NoOp in production
97
+ const ActivePlugin =
98
+ process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
99
+ ```
100
+
101
+ ### With Class-Based Panel
102
+
103
+ ```tsx
104
+ import {
105
+ createReactPanel,
106
+ createReactPlugin,
107
+ } from '@tanstack/devtools-utils/react'
108
+
109
+ class MyDevtoolsCore {
110
+ mount(el: HTMLElement, theme: 'light' | 'dark') {
111
+ /* render into el */
112
+ }
113
+ unmount() {
114
+ /* cleanup */
115
+ }
116
+ }
117
+
118
+ const [MyPanel, NoOpPanel] = createReactPanel(MyDevtoolsCore)
119
+
120
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
121
+ name: 'My Store',
122
+ Component: MyPanel,
123
+ })
124
+ ```
125
+
126
+ ## Framework API Differences
127
+
128
+ ### React & Preact -- Options Object
129
+
130
+ ```ts
131
+ createReactPlugin({ name, id?, defaultOpen?, Component }) // => [Plugin, NoOpPlugin]
132
+ createPreactPlugin({ name, id?, defaultOpen?, Component }) // => [Plugin, NoOpPlugin]
133
+ ```
134
+
135
+ - `Component` receives `DevtoolsPanelProps` (with `theme`).
136
+ - `Plugin()` returns `{ name, id?, defaultOpen?, render(el, theme) }`.
137
+ - Preact is identical to React but uses Preact JSX types and `preact/hooks`.
138
+
139
+ ### Vue -- Positional Arguments, NOT Options Object
140
+
141
+ ```ts
142
+ createVuePlugin(name: string, component: DefineComponent) // => [Plugin, NoOpPlugin]
143
+ ```
144
+
145
+ - Takes `(name, component)` as separate arguments, NOT an options object.
146
+ - `Plugin(props)` returns `{ name, component, props }` -- it passes props through.
147
+ - `NoOpPlugin(props)` returns `{ name, component: Fragment, props }`.
148
+ - Vue's `DevtoolsPanelProps.theme` also accepts `'system'`.
149
+
150
+ ### Solid -- Same API as React, Different Internals
151
+
152
+ ```ts
153
+ createSolidPlugin({ name, id?, defaultOpen?, Component }) // => [Plugin, NoOpPlugin]
154
+ ```
155
+
156
+ - Same options-object API as React.
157
+ - `Component` must be a Solid component function `(props: DevtoolsPanelProps) => JSX.Element`.
158
+ - The render function internally returns `<Component theme={theme} />` -- Solid handles reactivity.
159
+ - Solid also exports `constructCoreClass` from `@tanstack/devtools-utils/solid/class` for building lazy-loaded devtools cores.
160
+
161
+ ## Common Mistakes
162
+
163
+ ### CRITICAL: Using React JSX Pattern in Vue Adapter
164
+
165
+ Vue uses positional `(name, component)` arguments, NOT an options object.
166
+
167
+ ```ts
168
+ // WRONG -- will fail at compile time or produce garbage at runtime
169
+ const [MyPlugin, NoOpPlugin] = createVuePlugin({
170
+ name: 'My Plugin',
171
+ Component: MyPanel,
172
+ })
173
+
174
+ // CORRECT
175
+ const [MyPlugin, NoOpPlugin] = createVuePlugin('My Plugin', MyPanel)
176
+ ```
177
+
178
+ Vue plugins also work differently at call time -- you pass props:
179
+
180
+ ```ts
181
+ // WRONG -- calling Plugin() with no args (React pattern)
182
+ const plugin = MyPlugin()
183
+
184
+ // CORRECT -- Vue Plugin takes props
185
+ const plugin = MyPlugin({ theme: 'dark' })
186
+ ```
187
+
188
+ ### CRITICAL: Solid Render Prop Not Wrapped in Function
189
+
190
+ When using Solid components, `Component` must be a function reference, not raw JSX.
191
+
192
+ ```tsx
193
+ // WRONG -- evaluates immediately, breaks Solid reactivity
194
+ createSolidPlugin({
195
+ name: 'My Store',
196
+ Component: <MyPanel />, // This is JSX.Element, not a component function
197
+ })
198
+
199
+ // CORRECT -- pass the component function itself
200
+ createSolidPlugin({
201
+ name: 'My Store',
202
+ Component: (props) => <MyPanel theme={props.theme} />,
203
+ })
204
+
205
+ // ALSO CORRECT -- pass the component reference directly
206
+ createSolidPlugin({
207
+ name: 'My Store',
208
+ Component: MyPanel,
209
+ })
210
+ ```
211
+
212
+ ### HIGH: Ignoring NoOp Variant for Production
213
+
214
+ The factory returns `[Plugin, NoOpPlugin]`. Both must be destructured and used for proper tree-shaking.
215
+
216
+ ```tsx
217
+ // WRONG -- NoOp variant discarded, devtools code ships to production
218
+ const [MyPlugin] = createReactPlugin({ name: 'Store', Component: MyPanel })
219
+
220
+ // CORRECT -- conditionally use NoOp in production
221
+ const [MyPlugin, NoOpPlugin] = createReactPlugin({
222
+ name: 'Store',
223
+ Component: MyPanel,
224
+ })
225
+ const ActivePlugin =
226
+ process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
227
+ ```
228
+
229
+ ### MEDIUM: Not Passing Theme Prop to Panel Component
230
+
231
+ `DevtoolsPanelProps` includes `theme`. The devtools shell passes it so panels can match light/dark mode. If your component ignores it, the panel will not adapt to theme changes.
232
+
233
+ ```tsx
234
+ // WRONG -- theme is ignored
235
+ const Component = () => <div>My Panel</div>
236
+
237
+ // CORRECT -- use theme for styling
238
+ const Component = ({ theme }: DevtoolsPanelProps) => (
239
+ <div className={theme === 'dark' ? 'dark-mode' : 'light-mode'}>My Panel</div>
240
+ )
241
+ ```
242
+
243
+ ## Design Tension
244
+
245
+ The core architecture is framework-agnostic, but each framework has different idioms:
246
+
247
+ - React/Preact use an options object with `Component` as a JSX function component.
248
+ - Vue uses positional arguments with a `DefineComponent` and passes props through.
249
+ - Solid uses the same options API as React but with Solid's JSX and reactivity model.
250
+
251
+ Agents trained on React patterns will get Vue wrong. Always check the import path to determine which factory API to use.
252
+
253
+ ## Cross-References
254
+
255
+ - **devtools-plugin-panel** -- Build your panel component first, then wrap it with the appropriate framework adapter.
256
+ - **devtools-production** -- NoOp variants are the primary mechanism for stripping devtools from production bundles.
257
+
258
+ ## Reference Files
259
+
260
+ - `references/react.md` -- Full React factory API and examples
261
+ - `references/vue.md` -- Full Vue factory API and examples (different from React)
262
+ - `references/solid.md` -- Full Solid factory API and examples
263
+ - `references/preact.md` -- Full Preact factory API and examples
@@ -0,0 +1,262 @@
1
+ # Preact Framework Adapter Reference
2
+
3
+ ## Import
4
+
5
+ ```ts
6
+ import {
7
+ createPreactPlugin,
8
+ createPreactPanel,
9
+ } from '@tanstack/devtools-utils/preact'
10
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/preact'
11
+ ```
12
+
13
+ ## DevtoolsPanelProps
14
+
15
+ ```ts
16
+ interface DevtoolsPanelProps {
17
+ theme?: 'light' | 'dark'
18
+ }
19
+ ```
20
+
21
+ ## createPreactPlugin
22
+
23
+ Creates a `[Plugin, NoOpPlugin]` tuple from a Preact component and plugin metadata. Identical API to `createReactPlugin`.
24
+
25
+ ### Signature
26
+
27
+ ```ts
28
+ function createPreactPlugin(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 | Preact 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, theme) }`. 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/preact/plugin.tsx
56
+ /** @jsxImportSource preact */
57
+ import type { JSX } from 'preact'
58
+ import type { DevtoolsPanelProps } from './panel'
59
+
60
+ export function createPreactPlugin({
61
+ Component,
62
+ ...config
63
+ }: {
64
+ name: string
65
+ id?: string
66
+ defaultOpen?: boolean
67
+ Component: (props: DevtoolsPanelProps) => JSX.Element
68
+ }) {
69
+ function Plugin() {
70
+ return {
71
+ ...config,
72
+ render: (_el: HTMLElement, theme: 'light' | 'dark') => (
73
+ <Component theme={theme} />
74
+ ),
75
+ }
76
+ }
77
+ function NoOpPlugin() {
78
+ return {
79
+ ...config,
80
+ render: (_el: HTMLElement, _theme: 'light' | 'dark') => <></>,
81
+ }
82
+ }
83
+ return [Plugin, NoOpPlugin] as const
84
+ }
85
+ ```
86
+
87
+ ### Usage
88
+
89
+ #### Basic
90
+
91
+ ```tsx
92
+ import { createPreactPlugin } from '@tanstack/devtools-utils/preact'
93
+
94
+ function MyStorePanel({ theme }: { theme?: 'light' | 'dark' }) {
95
+ return (
96
+ <div class={theme === 'dark' ? 'dark' : 'light'}>
97
+ <h2>My Store Devtools</h2>
98
+ </div>
99
+ )
100
+ }
101
+
102
+ const [MyPlugin, NoOpPlugin] = createPreactPlugin({
103
+ name: 'My Store',
104
+ id: 'my-store',
105
+ defaultOpen: false,
106
+ Component: MyStorePanel,
107
+ })
108
+ ```
109
+
110
+ #### Inline Component
111
+
112
+ ```tsx
113
+ const [MyPlugin, NoOpPlugin] = createPreactPlugin({
114
+ name: 'My Store',
115
+ Component: ({ theme }) => <MyStorePanel theme={theme} />,
116
+ })
117
+ ```
118
+
119
+ #### Production Tree-Shaking
120
+
121
+ ```tsx
122
+ const [MyPlugin, NoOpPlugin] = createPreactPlugin({
123
+ name: 'My Store',
124
+ Component: MyStorePanel,
125
+ })
126
+
127
+ const ActivePlugin =
128
+ process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
129
+ ```
130
+
131
+ ## createPreactPanel
132
+
133
+ Wraps a class-based devtools core in a Preact component. Identical behavior to `createReactPanel` but uses `preact/hooks`.
134
+
135
+ ### Signature
136
+
137
+ ```ts
138
+ function createPreactPanel<
139
+ TComponentProps extends DevtoolsPanelProps | undefined,
140
+ TCoreDevtoolsClass extends {
141
+ mount: (el: HTMLElement, theme: 'light' | 'dark') => void
142
+ unmount: () => void
143
+ },
144
+ >(CoreClass: new () => TCoreDevtoolsClass): readonly [Panel, NoOpPanel]
145
+ ```
146
+
147
+ ### Parameters
148
+
149
+ | Parameter | Type | Required | Description |
150
+ | ----------- | ------------------------------------------------------- | -------- | --------------------------------------- |
151
+ | `CoreClass` | `new () => { mount(el, theme): void; unmount(): void }` | Yes | Class constructor for the devtools core |
152
+
153
+ ### Return Value
154
+
155
+ - **`Panel`** -- A Preact component that:
156
+ - Creates a `<div style={{ height: '100%' }}>` with a ref.
157
+ - Instantiates `CoreClass` on mount via `useEffect` (from `preact/hooks`).
158
+ - Calls `core.mount(el, props.theme ?? 'dark')`.
159
+ - Calls `core.unmount()` on cleanup.
160
+ - Re-runs the effect when `theme` prop changes.
161
+ - **`NoOpPanel`** -- Renders `<></>`.
162
+
163
+ ### Source
164
+
165
+ ```tsx
166
+ // packages/devtools-utils/src/preact/panel.tsx
167
+ /** @jsxImportSource preact */
168
+ import { useEffect, useRef } from 'preact/hooks'
169
+
170
+ export function createPreactPanel<
171
+ TComponentProps extends DevtoolsPanelProps | undefined,
172
+ TCoreDevtoolsClass extends {
173
+ mount: (el: HTMLElement, theme: 'light' | 'dark') => void
174
+ unmount: () => void
175
+ },
176
+ >(CoreClass: new () => TCoreDevtoolsClass) {
177
+ function Panel(props: TComponentProps) {
178
+ const devToolRef = useRef<HTMLDivElement>(null)
179
+ const devtools = useRef<TCoreDevtoolsClass | null>(null)
180
+ useEffect(() => {
181
+ if (devtools.current) return
182
+ devtools.current = new CoreClass()
183
+ if (devToolRef.current) {
184
+ devtools.current.mount(devToolRef.current, props?.theme ?? 'dark')
185
+ }
186
+ return () => {
187
+ if (devToolRef.current) {
188
+ devtools.current?.unmount()
189
+ devtools.current = null
190
+ }
191
+ }
192
+ }, [props?.theme])
193
+ return <div style={{ height: '100%' }} ref={devToolRef} />
194
+ }
195
+
196
+ function NoOpPanel(_props: TComponentProps) {
197
+ return <></>
198
+ }
199
+ return [Panel, NoOpPanel] as const
200
+ }
201
+ ```
202
+
203
+ ### Usage
204
+
205
+ ```tsx
206
+ import {
207
+ createPreactPanel,
208
+ createPreactPlugin,
209
+ } from '@tanstack/devtools-utils/preact'
210
+
211
+ class MyDevtoolsCore {
212
+ mount(el: HTMLElement, theme: 'light' | 'dark') {
213
+ // Use DOM APIs to render your devtools UI into the provided element
214
+ const container = document.createElement('div')
215
+ container.className = theme
216
+ container.textContent = 'Devtools loaded'
217
+ el.appendChild(container)
218
+ }
219
+ unmount() {
220
+ // cleanup
221
+ }
222
+ }
223
+
224
+ // Step 1: Create panel from class
225
+ const [MyPanel, NoOpPanel] = createPreactPanel(MyDevtoolsCore)
226
+
227
+ // Step 2: Create plugin from panel
228
+ const [MyPlugin, NoOpPlugin] = createPreactPlugin({
229
+ name: 'My Store',
230
+ Component: MyPanel,
231
+ })
232
+
233
+ // Step 3: Conditional for production
234
+ const ActivePlugin =
235
+ process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
236
+ ```
237
+
238
+ ## Preact-Specific Notes
239
+
240
+ 1. **Identical to React API.** `createPreactPlugin` and `createPreactPanel` have the exact same API signatures as their React counterparts. The only difference is the JSX runtime (`preact` vs `react`) and hooks import (`preact/hooks` vs `react`).
241
+
242
+ 2. **Use `class` not `className`.** Preact supports both, but idiomatic Preact uses `class` in JSX.
243
+
244
+ 3. **No Strict Mode double-mount by default.** Preact does not have React's Strict Mode double-invocation behavior, but the ref guard (`if (devtools.current) return`) is still present and harmless.
245
+
246
+ 4. **Default theme is `'dark'`.** If `props.theme` is undefined, the panel defaults to `'dark'`.
247
+
248
+ 5. **Same hooks behavior.** The `useEffect` dependency on `[props?.theme]` means the panel re-mounts when theme changes, same as React.
249
+
250
+ ## Comparison with React
251
+
252
+ | Aspect | React | Preact |
253
+ | ------------------------- | -------------------------------- | --------------------------------- |
254
+ | Import path | `@tanstack/devtools-utils/react` | `@tanstack/devtools-utils/preact` |
255
+ | JSX types | `react` JSX | `preact` JSX |
256
+ | Hooks import | `react` | `preact/hooks` |
257
+ | API shape | Identical | Identical |
258
+ | `createXPlugin` signature | Same | Same |
259
+ | `createXPanel` signature | Same | Same |
260
+ | `DevtoolsPanelProps` | Same | Same |
261
+
262
+ If you have working React adapter code, converting to Preact is a matter of changing the import paths.
@@ -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.
@@ -0,0 +1,270 @@
1
+ # Vue Framework Adapter Reference
2
+
3
+ ## Import
4
+
5
+ ```ts
6
+ import { createVuePlugin, createVuePanel } from '@tanstack/devtools-utils/vue'
7
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
8
+ ```
9
+
10
+ ## DevtoolsPanelProps
11
+
12
+ ```ts
13
+ // NOTE: Vue includes 'system' -- unlike React/Solid/Preact
14
+ interface DevtoolsPanelProps {
15
+ theme?: 'dark' | 'light' | 'system'
16
+ }
17
+ ```
18
+
19
+ ## createVuePlugin
20
+
21
+ Creates a `[Plugin, NoOpPlugin]` tuple from a Vue component.
22
+
23
+ **CRITICAL: Vue uses positional arguments `(name, component)`, NOT an options object.**
24
+
25
+ ### Signature
26
+
27
+ ```ts
28
+ function createVuePlugin<TComponentProps extends Record<string, any>>(
29
+ name: string,
30
+ component: DefineComponent<TComponentProps, {}, unknown>,
31
+ ): readonly [Plugin, NoOpPlugin]
32
+ ```
33
+
34
+ ### Parameters
35
+
36
+ | Parameter | Type | Required | Description |
37
+ | ----------- | ---------------------------------- | -------- | -------------------------------------- |
38
+ | `name` | `string` | Yes | Display name shown in the devtools tab |
39
+ | `component` | `DefineComponent<TComponentProps>` | Yes | Vue component to render in the panel |
40
+
41
+ Note: There is **no** `id` or `defaultOpen` parameter. Vue plugins are simpler.
42
+
43
+ ### Return Value
44
+
45
+ A `readonly [Plugin, NoOpPlugin]` tuple. Both are **functions that accept props**:
46
+
47
+ - **`Plugin(props: TComponentProps)`** -- returns `{ name, component, props }` where `component` is your Vue component.
48
+ - **`NoOpPlugin(props: TComponentProps)`** -- returns `{ name, component: Fragment, props }` where `Fragment` is Vue's built-in fragment (renders nothing visible).
49
+
50
+ **This differs from React/Solid/Preact** where `Plugin()` takes no arguments.
51
+
52
+ ### Source
53
+
54
+ ```ts
55
+ // packages/devtools-utils/src/vue/plugin.ts
56
+ import { Fragment } from 'vue'
57
+ import type { DefineComponent } from 'vue'
58
+
59
+ export function createVuePlugin<TComponentProps extends Record<string, any>>(
60
+ name: string,
61
+ component: DefineComponent<TComponentProps, {}, unknown>,
62
+ ) {
63
+ function Plugin(props: TComponentProps) {
64
+ return {
65
+ name,
66
+ component,
67
+ props,
68
+ }
69
+ }
70
+ function NoOpPlugin(props: TComponentProps) {
71
+ return {
72
+ name,
73
+ component: Fragment,
74
+ props,
75
+ }
76
+ }
77
+ return [Plugin, NoOpPlugin] as const
78
+ }
79
+ ```
80
+
81
+ ### Usage
82
+
83
+ #### Basic
84
+
85
+ ```vue
86
+ <!-- MyStorePanel.vue -->
87
+ <script setup lang="ts">
88
+ import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
89
+
90
+ const props = defineProps<DevtoolsPanelProps>()
91
+ </script>
92
+
93
+ <template>
94
+ <div :class="props.theme">
95
+ <h2>My Store Devtools</h2>
96
+ </div>
97
+ </template>
98
+ ```
99
+
100
+ ```ts
101
+ import { createVuePlugin } from '@tanstack/devtools-utils/vue'
102
+ import MyStorePanel from './MyStorePanel.vue'
103
+
104
+ const [MyPlugin, NoOpPlugin] = createVuePlugin('My Store', MyStorePanel)
105
+ ```
106
+
107
+ #### Using the Plugin (Vue-specific -- pass props)
108
+
109
+ ```ts
110
+ // Vue plugins are called WITH props
111
+ const plugin = MyPlugin({ theme: 'dark' })
112
+ // Returns: { name: 'My Store', component: MyStorePanel, props: { theme: 'dark' } }
113
+
114
+ const noopPlugin = NoOpPlugin({ theme: 'dark' })
115
+ // Returns: { name: 'My Store', component: Fragment, props: { theme: 'dark' } }
116
+ ```
117
+
118
+ #### Production Tree-Shaking
119
+
120
+ ```ts
121
+ const [MyPlugin, NoOpPlugin] = createVuePlugin('My Store', MyStorePanel)
122
+
123
+ const ActivePlugin = import.meta.env.DEV ? MyPlugin : NoOpPlugin
124
+ ```
125
+
126
+ ## createVuePanel
127
+
128
+ Wraps a class-based devtools core in a Vue `defineComponent`.
129
+
130
+ ### Signature
131
+
132
+ ```ts
133
+ function createVuePanel<
134
+ TComponentProps extends DevtoolsPanelProps,
135
+ TCoreDevtoolsClass extends {
136
+ mount: (el: HTMLElement, theme?: DevtoolsPanelProps['theme']) => void
137
+ unmount: () => void
138
+ },
139
+ >(
140
+ CoreClass: new (props: TComponentProps) => TCoreDevtoolsClass,
141
+ ): readonly [Panel, NoOpPanel]
142
+ ```
143
+
144
+ ### Parameters
145
+
146
+ | Parameter | Type | Required | Description |
147
+ | ----------- | ------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------ |
148
+ | `CoreClass` | `new (props: TComponentProps) => { mount(el, theme?): void; unmount(): void }` | Yes | Class constructor. **Note:** Vue's constructor takes `props`, unlike React's no-arg constructor. |
149
+
150
+ ### Return Value
151
+
152
+ A tuple of two Vue `DefineComponent`s:
153
+
154
+ - **`Panel`** -- Accepts `theme` and `devtoolsProps` as props. On `onMounted`, instantiates `CoreClass(devtoolsProps)` and calls `mount(el, theme)`. On `onUnmounted`, calls `unmount()`. Renders a `<div style="height:100%">`.
155
+ - **`NoOpPanel`** -- Renders `null`.
156
+
157
+ Both components have the type:
158
+
159
+ ```ts
160
+ DefineComponent<{
161
+ theme?: 'dark' | 'light' | 'system'
162
+ devtoolsProps: TComponentProps
163
+ }>
164
+ ```
165
+
166
+ ### Source
167
+
168
+ ```ts
169
+ // packages/devtools-utils/src/vue/panel.ts
170
+ export function createVuePanel<
171
+ TComponentProps extends DevtoolsPanelProps,
172
+ TCoreDevtoolsClass extends {
173
+ mount: (el: HTMLElement, theme?: DevtoolsPanelProps['theme']) => void
174
+ unmount: () => void
175
+ },
176
+ >(CoreClass: new (props: TComponentProps) => TCoreDevtoolsClass) {
177
+ const props = {
178
+ theme: { type: String as () => DevtoolsPanelProps['theme'] },
179
+ devtoolsProps: { type: Object as () => TComponentProps },
180
+ }
181
+
182
+ const Panel = defineComponent({
183
+ props,
184
+ setup(config) {
185
+ const devToolRef = ref<HTMLElement | null>(null)
186
+ const devtools = ref<TCoreDevtoolsClass | null>(null)
187
+ onMounted(() => {
188
+ const instance = new CoreClass(config.devtoolsProps as TComponentProps)
189
+ devtools.value = instance
190
+ if (devToolRef.value) {
191
+ instance.mount(devToolRef.value, config.theme)
192
+ }
193
+ })
194
+ onUnmounted(() => {
195
+ if (devToolRef.value && devtools.value) {
196
+ devtools.value.unmount()
197
+ }
198
+ })
199
+ return () => h('div', { style: { height: '100%' }, ref: devToolRef })
200
+ },
201
+ })
202
+
203
+ const NoOpPanel = defineComponent({
204
+ props,
205
+ setup() {
206
+ return () => null
207
+ },
208
+ })
209
+
210
+ return [Panel, NoOpPanel] as unknown as [
211
+ DefineComponent<{
212
+ theme?: DevtoolsPanelProps['theme']
213
+ devtoolsProps: TComponentProps
214
+ }>,
215
+ DefineComponent<{
216
+ theme?: DevtoolsPanelProps['theme']
217
+ devtoolsProps: TComponentProps
218
+ }>,
219
+ ]
220
+ }
221
+ ```
222
+
223
+ ### Usage
224
+
225
+ ```ts
226
+ import { createVuePanel, createVuePlugin } from '@tanstack/devtools-utils/vue'
227
+
228
+ class MyDevtoolsCore {
229
+ constructor(private props: { theme?: string }) {}
230
+ mount(el: HTMLElement, theme?: 'dark' | 'light' | 'system') {
231
+ // render into el
232
+ }
233
+ unmount() {
234
+ // cleanup
235
+ }
236
+ }
237
+
238
+ // Step 1: Create panel from class
239
+ const [MyPanel, NoOpPanel] = createVuePanel(MyDevtoolsCore)
240
+
241
+ // Step 2: Create plugin from panel
242
+ const [MyPlugin, NoOpPlugin] = createVuePlugin('My Store', MyPanel)
243
+ ```
244
+
245
+ #### Using the Panel Directly in a Template
246
+
247
+ ```vue
248
+ <template>
249
+ <MyPanel
250
+ theme="dark"
251
+ :devtools-props="{
252
+ /* ... */
253
+ }"
254
+ />
255
+ </template>
256
+ ```
257
+
258
+ ## Vue-Specific Gotchas
259
+
260
+ 1. **Positional arguments, not options object.** This is the most common mistake. `createVuePlugin('name', Component)`, not `createVuePlugin({ name, Component })`.
261
+
262
+ 2. **Plugin functions accept props.** `MyPlugin(props)` returns `{ name, component, props }`. This differs from React/Solid/Preact where `Plugin()` takes no arguments.
263
+
264
+ 3. **Theme includes `'system'`.** Vue's `DevtoolsPanelProps` accepts `'dark' | 'light' | 'system'`, while all other frameworks only accept `'light' | 'dark'`.
265
+
266
+ 4. **No `id` or `defaultOpen`.** The Vue `createVuePlugin` API only takes `name` and `component`. There is no `id` or `defaultOpen` parameter.
267
+
268
+ 5. **Panel constructor takes props.** `createVuePanel`'s `CoreClass` constructor receives props: `new CoreClass(devtoolsProps)`. React/Preact/Solid constructors take no arguments.
269
+
270
+ 6. **`devtoolsProps` prop on panel.** The Vue panel component has a separate `devtoolsProps` prop for forwarding data to the core class, in addition to the `theme` prop.