@tanstack/devtools-utils 0.3.3 → 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 +20 -0
- package/package.json +7 -2
- package/skills/devtools-framework-adapters/SKILL.md +263 -0
- package/skills/devtools-framework-adapters/references/preact.md +262 -0
- package/skills/devtools-framework-adapters/references/react.md +241 -0
- package/skills/devtools-framework-adapters/references/solid.md +274 -0
- package/skills/devtools-framework-adapters/references/vue.md +270 -0
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.
|
|
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.
|