@tanstack/devtools 0.6.24 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk/{XF4JFOLU.js → VZEY7HNC.js} +32 -9
- package/dist/dev.js +3 -3
- package/dist/devtools/{UUNAZSBD.js → 7NDEDZB7.js} +2 -2
- package/dist/devtools/{OBIHU6L6.js → JEZZ2PQE.js} +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -3
- package/dist/server.js +2 -2
- package/package.json +3 -3
- package/src/context/devtools-context.test.ts +268 -1
- package/src/context/devtools-context.tsx +29 -9
- package/src/context/use-devtools-context.ts +2 -1
- package/src/utils/constants.ts +4 -0
- package/src/utils/get-default-active-plugins.test.ts +194 -0
- package/src/utils/get-default-active-plugins.ts +36 -0
|
@@ -182,6 +182,22 @@ var usePiPWindow = () => {
|
|
|
182
182
|
});
|
|
183
183
|
return context;
|
|
184
184
|
};
|
|
185
|
+
|
|
186
|
+
// src/utils/constants.ts
|
|
187
|
+
var MAX_ACTIVE_PLUGINS = 3;
|
|
188
|
+
|
|
189
|
+
// src/utils/get-default-active-plugins.ts
|
|
190
|
+
function getDefaultActivePlugins(plugins) {
|
|
191
|
+
if (plugins.length === 0) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
if (plugins.length === 1) {
|
|
195
|
+
return [plugins[0].id];
|
|
196
|
+
}
|
|
197
|
+
return plugins.filter((plugin) => plugin.defaultOpen === true).slice(0, MAX_ACTIVE_PLUGINS).map((plugin) => plugin.id);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/context/devtools-context.tsx
|
|
185
201
|
var DevtoolsContext = createContext();
|
|
186
202
|
var getSettings = () => {
|
|
187
203
|
const settingsString = getStorageItem(TANSTACK_DEVTOOLS_SETTINGS);
|
|
@@ -215,18 +231,25 @@ function getStateFromLocalStorage(plugins) {
|
|
|
215
231
|
var getExistingStateFromStorage = (config, plugins) => {
|
|
216
232
|
const existingState = getStateFromLocalStorage(plugins);
|
|
217
233
|
const settings = getSettings();
|
|
234
|
+
const pluginsWithIds = plugins?.map((plugin, i) => {
|
|
235
|
+
const id = generatePluginId(plugin, i);
|
|
236
|
+
return {
|
|
237
|
+
...plugin,
|
|
238
|
+
id
|
|
239
|
+
};
|
|
240
|
+
}) || [];
|
|
241
|
+
let activePlugins = existingState?.activePlugins || [];
|
|
242
|
+
const shouldFillWithDefaultOpenPlugins = activePlugins.length === 0 && pluginsWithIds.length > 0;
|
|
243
|
+
if (shouldFillWithDefaultOpenPlugins) {
|
|
244
|
+
activePlugins = getDefaultActivePlugins(pluginsWithIds);
|
|
245
|
+
}
|
|
218
246
|
const state = {
|
|
219
247
|
...initialState,
|
|
220
|
-
plugins:
|
|
221
|
-
const id = generatePluginId(plugin, i);
|
|
222
|
-
return {
|
|
223
|
-
...plugin,
|
|
224
|
-
id
|
|
225
|
-
};
|
|
226
|
-
}) || [],
|
|
248
|
+
plugins: pluginsWithIds,
|
|
227
249
|
state: {
|
|
228
250
|
...initialState.state,
|
|
229
|
-
...existingState
|
|
251
|
+
...existingState,
|
|
252
|
+
activePlugins
|
|
230
253
|
},
|
|
231
254
|
settings: {
|
|
232
255
|
...initialState.settings,
|
|
@@ -277,4 +300,4 @@ var DevtoolsProvider = (props) => {
|
|
|
277
300
|
});
|
|
278
301
|
};
|
|
279
302
|
|
|
280
|
-
export { DevtoolsContext, DevtoolsProvider, PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID, PiPProvider, TANSTACK_DEVTOOLS, getAllPermutations, initialState, keyboardModifiers, uppercaseFirstLetter, usePiPWindow };
|
|
303
|
+
export { DevtoolsContext, DevtoolsProvider, MAX_ACTIVE_PLUGINS, PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID, PiPProvider, TANSTACK_DEVTOOLS, getAllPermutations, initialState, keyboardModifiers, uppercaseFirstLetter, usePiPWindow };
|
package/dist/dev.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { initialState, DevtoolsProvider, PiPProvider } from './chunk/
|
|
2
|
-
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/
|
|
1
|
+
import { initialState, DevtoolsProvider, PiPProvider } from './chunk/VZEY7HNC.js';
|
|
2
|
+
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/VZEY7HNC.js';
|
|
3
3
|
import { render, createComponent, Portal } from 'solid-js/web';
|
|
4
4
|
import { lazy } from 'solid-js';
|
|
5
5
|
import { ClientEventBus } from '@tanstack/devtools-event-bus/client';
|
|
@@ -30,7 +30,7 @@ var TanStackDevtoolsCore = class {
|
|
|
30
30
|
const mountTo = el;
|
|
31
31
|
const dispose = render(() => {
|
|
32
32
|
const _self$ = this;
|
|
33
|
-
this.#Component = lazy(() => import('./devtools/
|
|
33
|
+
this.#Component = lazy(() => import('./devtools/7NDEDZB7.js'));
|
|
34
34
|
const Devtools = this.#Component;
|
|
35
35
|
this.#eventBus = new ClientEventBus(this.#eventBusConfig);
|
|
36
36
|
this.#eventBus.start();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { usePiPWindow, keyboardModifiers, getAllPermutations, TANSTACK_DEVTOOLS, DevtoolsContext, PLUGIN_TITLE_CONTAINER_ID, PLUGIN_CONTAINER_ID, uppercaseFirstLetter } from '../chunk/
|
|
1
|
+
import { usePiPWindow, keyboardModifiers, getAllPermutations, TANSTACK_DEVTOOLS, DevtoolsContext, PLUGIN_TITLE_CONTAINER_ID, PLUGIN_CONTAINER_ID, uppercaseFirstLetter, MAX_ACTIVE_PLUGINS } from '../chunk/VZEY7HNC.js';
|
|
2
2
|
import { delegateEvents, createComponent, Portal, template, use, setAttribute, insert, memo, effect, className, addEventListener, style, classList } from 'solid-js/web';
|
|
3
3
|
import { createContext, createSignal, createEffect, Show, createMemo, For, useContext, onCleanup, onMount } from 'solid-js';
|
|
4
4
|
import { createShortcut, useKeyDownList } from '@solid-primitives/keyboard';
|
|
@@ -94,7 +94,7 @@ var usePlugins = () => {
|
|
|
94
94
|
setStore((prev) => {
|
|
95
95
|
const isActive = prev.state.activePlugins.includes(pluginId);
|
|
96
96
|
const updatedPlugins = isActive ? prev.state.activePlugins.filter((id) => id !== pluginId) : [...prev.state.activePlugins, pluginId];
|
|
97
|
-
if (updatedPlugins.length >
|
|
97
|
+
if (updatedPlugins.length > MAX_ACTIVE_PLUGINS) return prev;
|
|
98
98
|
return {
|
|
99
99
|
...prev,
|
|
100
100
|
state: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { usePiPWindow, keyboardModifiers, getAllPermutations, TANSTACK_DEVTOOLS, DevtoolsContext, PLUGIN_TITLE_CONTAINER_ID, PLUGIN_CONTAINER_ID, uppercaseFirstLetter } from '../chunk/
|
|
1
|
+
import { usePiPWindow, keyboardModifiers, getAllPermutations, TANSTACK_DEVTOOLS, DevtoolsContext, PLUGIN_TITLE_CONTAINER_ID, PLUGIN_CONTAINER_ID, uppercaseFirstLetter, MAX_ACTIVE_PLUGINS } from '../chunk/VZEY7HNC.js';
|
|
2
2
|
import { createComponent, Portal, ssr, ssrAttribute, escape, ssrStyle } from 'solid-js/web';
|
|
3
3
|
import { createContext, createSignal, createEffect, Show, createMemo, For, useContext, onCleanup, onMount } from 'solid-js';
|
|
4
4
|
import { createShortcut, useKeyDownList } from '@solid-primitives/keyboard';
|
|
@@ -94,7 +94,7 @@ var usePlugins = () => {
|
|
|
94
94
|
setStore((prev) => {
|
|
95
95
|
const isActive = prev.state.activePlugins.includes(pluginId);
|
|
96
96
|
const updatedPlugins = isActive ? prev.state.activePlugins.filter((id) => id !== pluginId) : [...prev.state.activePlugins, pluginId];
|
|
97
|
-
if (updatedPlugins.length >
|
|
97
|
+
if (updatedPlugins.length > MAX_ACTIVE_PLUGINS) return prev;
|
|
98
98
|
return {
|
|
99
99
|
...prev,
|
|
100
100
|
state: {
|
package/dist/index.d.ts
CHANGED
|
@@ -120,6 +120,12 @@ interface TanStackDevtoolsPlugin {
|
|
|
120
120
|
* If not provided, it will be generated based on the name.
|
|
121
121
|
*/
|
|
122
122
|
id?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Whether the plugin should be open by default when there are no active plugins.
|
|
125
|
+
* If true, this plugin will be added to activePlugins on initial load when activePlugins is empty.
|
|
126
|
+
* @default false
|
|
127
|
+
*/
|
|
128
|
+
defaultOpen?: boolean;
|
|
123
129
|
/**
|
|
124
130
|
* Render the plugin UI by using the provided element. This function will be called
|
|
125
131
|
* when the plugin tab is clicked and expected to be mounted.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { initialState, DevtoolsProvider, PiPProvider } from './chunk/
|
|
2
|
-
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/
|
|
1
|
+
import { initialState, DevtoolsProvider, PiPProvider } from './chunk/VZEY7HNC.js';
|
|
2
|
+
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/VZEY7HNC.js';
|
|
3
3
|
import { render, createComponent, Portal } from 'solid-js/web';
|
|
4
4
|
import { lazy } from 'solid-js';
|
|
5
5
|
import { ClientEventBus } from '@tanstack/devtools-event-bus/client';
|
|
@@ -30,7 +30,7 @@ var TanStackDevtoolsCore = class {
|
|
|
30
30
|
const mountTo = el;
|
|
31
31
|
const dispose = render(() => {
|
|
32
32
|
const _self$ = this;
|
|
33
|
-
this.#Component = lazy(() => import('./devtools/
|
|
33
|
+
this.#Component = lazy(() => import('./devtools/7NDEDZB7.js'));
|
|
34
34
|
const Devtools = this.#Component;
|
|
35
35
|
this.#eventBus = new ClientEventBus(this.#eventBusConfig);
|
|
36
36
|
this.#eventBus.start();
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { initialState } from './chunk/
|
|
2
|
-
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/
|
|
1
|
+
import { initialState } from './chunk/VZEY7HNC.js';
|
|
2
|
+
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/VZEY7HNC.js';
|
|
3
3
|
import 'solid-js/web';
|
|
4
4
|
import 'solid-js';
|
|
5
5
|
import '@tanstack/devtools-event-bus/client';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/devtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "TanStack Devtools is a set of tools for building advanced devtools for your application.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"goober": "^2.1.16",
|
|
57
57
|
"solid-js": "^1.9.9",
|
|
58
58
|
"@tanstack/devtools-client": "0.0.3",
|
|
59
|
-
"@tanstack/devtools-
|
|
60
|
-
"@tanstack/devtools-
|
|
59
|
+
"@tanstack/devtools-ui": "0.4.4",
|
|
60
|
+
"@tanstack/devtools-event-bus": "0.3.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"solid-js": ">=1.9.7"
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
2
|
import { TANSTACK_DEVTOOLS_STATE } from '../utils/storage'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getExistingStateFromStorage,
|
|
5
|
+
getStateFromLocalStorage,
|
|
6
|
+
} from './devtools-context'
|
|
7
|
+
import type { TanStackDevtoolsPlugin } from './devtools-context'
|
|
4
8
|
|
|
5
9
|
describe('getStateFromLocalStorage', () => {
|
|
6
10
|
beforeEach(() => {
|
|
@@ -56,4 +60,267 @@ describe('getStateFromLocalStorage', () => {
|
|
|
56
60
|
const state = getStateFromLocalStorage(undefined)
|
|
57
61
|
expect(state).toEqual(undefined)
|
|
58
62
|
})
|
|
63
|
+
|
|
64
|
+
it('should return undefined when no localStorage state exists (allowing defaultOpen to be applied)', () => {
|
|
65
|
+
// No existing state in localStorage - this allows defaultOpen logic to trigger
|
|
66
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
67
|
+
{
|
|
68
|
+
id: 'plugin1',
|
|
69
|
+
render: () => {},
|
|
70
|
+
name: 'Plugin 1',
|
|
71
|
+
defaultOpen: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'plugin2',
|
|
75
|
+
render: () => {},
|
|
76
|
+
name: 'Plugin 2',
|
|
77
|
+
defaultOpen: false,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'plugin3',
|
|
81
|
+
render: () => {},
|
|
82
|
+
name: 'Plugin 3',
|
|
83
|
+
defaultOpen: true,
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
// When undefined is returned, getExistingStateFromStorage will fill activePlugins with defaultOpen plugins
|
|
88
|
+
const state = getStateFromLocalStorage(plugins)
|
|
89
|
+
expect(state).toEqual(undefined)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should preserve existing activePlugins from localStorage (defaultOpen should not override)', () => {
|
|
93
|
+
const mockState = {
|
|
94
|
+
activePlugins: ['plugin2'],
|
|
95
|
+
settings: {
|
|
96
|
+
theme: 'dark',
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState))
|
|
100
|
+
|
|
101
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
102
|
+
{
|
|
103
|
+
id: 'plugin1',
|
|
104
|
+
render: () => {},
|
|
105
|
+
name: 'Plugin 1',
|
|
106
|
+
defaultOpen: true,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'plugin2',
|
|
110
|
+
render: () => {},
|
|
111
|
+
name: 'Plugin 2',
|
|
112
|
+
defaultOpen: false,
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
const state = getStateFromLocalStorage(plugins)
|
|
117
|
+
// Should keep existing activePlugins - defaultOpen logic won't override in getExistingStateFromStorage
|
|
118
|
+
expect(state?.activePlugins).toEqual(['plugin2'])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should automatically activate a single plugin when no active plugins exist', () => {
|
|
122
|
+
// No existing state in localStorage
|
|
123
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
124
|
+
{
|
|
125
|
+
id: 'only-plugin',
|
|
126
|
+
render: () => {},
|
|
127
|
+
name: 'Only Plugin',
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
const state = getStateFromLocalStorage(plugins)
|
|
132
|
+
// Should return undefined - the single plugin activation happens in getExistingStateFromStorage
|
|
133
|
+
expect(state).toEqual(undefined)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('getExistingStateFromStorage - integration tests', () => {
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
localStorage.clear()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should automatically activate a single plugin when no localStorage state exists', () => {
|
|
143
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
144
|
+
{
|
|
145
|
+
id: 'only-plugin',
|
|
146
|
+
render: () => {},
|
|
147
|
+
name: 'Only Plugin',
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
const state = getExistingStateFromStorage(undefined, plugins)
|
|
152
|
+
expect(state.state.activePlugins).toEqual(['only-plugin'])
|
|
153
|
+
expect(state.plugins).toHaveLength(1)
|
|
154
|
+
expect(state.plugins![0]?.id).toBe('only-plugin')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should activate plugins with defaultOpen: true when no localStorage state exists', () => {
|
|
158
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
159
|
+
{
|
|
160
|
+
id: 'plugin1',
|
|
161
|
+
render: () => {},
|
|
162
|
+
name: 'Plugin 1',
|
|
163
|
+
defaultOpen: true,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'plugin2',
|
|
167
|
+
render: () => {},
|
|
168
|
+
name: 'Plugin 2',
|
|
169
|
+
defaultOpen: false,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'plugin3',
|
|
173
|
+
render: () => {},
|
|
174
|
+
name: 'Plugin 3',
|
|
175
|
+
defaultOpen: true,
|
|
176
|
+
},
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
const state = getExistingStateFromStorage(undefined, plugins)
|
|
180
|
+
expect(state.state.activePlugins).toEqual(['plugin1', 'plugin3'])
|
|
181
|
+
expect(state.plugins).toHaveLength(3)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('should limit defaultOpen plugins to MAX_ACTIVE_PLUGINS (3) when 5 have defaultOpen: true', () => {
|
|
185
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
186
|
+
{
|
|
187
|
+
id: 'plugin1',
|
|
188
|
+
render: () => {},
|
|
189
|
+
name: 'Plugin 1',
|
|
190
|
+
defaultOpen: true,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'plugin2',
|
|
194
|
+
render: () => {},
|
|
195
|
+
name: 'Plugin 2',
|
|
196
|
+
defaultOpen: true,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'plugin3',
|
|
200
|
+
render: () => {},
|
|
201
|
+
name: 'Plugin 3',
|
|
202
|
+
defaultOpen: true,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: 'plugin4',
|
|
206
|
+
render: () => {},
|
|
207
|
+
name: 'Plugin 4',
|
|
208
|
+
defaultOpen: true,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'plugin5',
|
|
212
|
+
render: () => {},
|
|
213
|
+
name: 'Plugin 5',
|
|
214
|
+
defaultOpen: true,
|
|
215
|
+
},
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
const state = getExistingStateFromStorage(undefined, plugins)
|
|
219
|
+
// Should only activate first 3 plugins
|
|
220
|
+
expect(state.state.activePlugins).toEqual(['plugin1', 'plugin2', 'plugin3'])
|
|
221
|
+
expect(state.state.activePlugins).toHaveLength(3)
|
|
222
|
+
expect(state.state.activePlugins).not.toContain('plugin4')
|
|
223
|
+
expect(state.state.activePlugins).not.toContain('plugin5')
|
|
224
|
+
// All 5 plugins should still be in the plugins array
|
|
225
|
+
expect(state.plugins).toHaveLength(5)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should preserve existing activePlugins from localStorage even when plugins have defaultOpen', () => {
|
|
229
|
+
const mockState = {
|
|
230
|
+
activePlugins: ['plugin2', 'plugin4'],
|
|
231
|
+
settings: {
|
|
232
|
+
theme: 'dark',
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState))
|
|
236
|
+
|
|
237
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
238
|
+
{
|
|
239
|
+
id: 'plugin1',
|
|
240
|
+
render: () => {},
|
|
241
|
+
name: 'Plugin 1',
|
|
242
|
+
defaultOpen: true,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'plugin2',
|
|
246
|
+
render: () => {},
|
|
247
|
+
name: 'Plugin 2',
|
|
248
|
+
defaultOpen: false,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'plugin3',
|
|
252
|
+
render: () => {},
|
|
253
|
+
name: 'Plugin 3',
|
|
254
|
+
defaultOpen: true,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'plugin4',
|
|
258
|
+
render: () => {},
|
|
259
|
+
name: 'Plugin 4',
|
|
260
|
+
defaultOpen: false,
|
|
261
|
+
},
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
const state = getExistingStateFromStorage(undefined, plugins)
|
|
265
|
+
// Should preserve the localStorage state, not use defaultOpen
|
|
266
|
+
expect(state.state.activePlugins).toEqual(['plugin2', 'plugin4'])
|
|
267
|
+
expect(state.plugins).toHaveLength(4)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should return empty activePlugins when no defaultOpen and multiple plugins', () => {
|
|
271
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
272
|
+
{
|
|
273
|
+
id: 'plugin1',
|
|
274
|
+
render: () => {},
|
|
275
|
+
name: 'Plugin 1',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: 'plugin2',
|
|
279
|
+
render: () => {},
|
|
280
|
+
name: 'Plugin 2',
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 'plugin3',
|
|
284
|
+
render: () => {},
|
|
285
|
+
name: 'Plugin 3',
|
|
286
|
+
},
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
const state = getExistingStateFromStorage(undefined, plugins)
|
|
290
|
+
expect(state.state.activePlugins).toEqual([])
|
|
291
|
+
expect(state.plugins).toHaveLength(3)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should handle single plugin with defaultOpen: false by activating it anyway', () => {
|
|
295
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
296
|
+
{
|
|
297
|
+
id: 'only-plugin',
|
|
298
|
+
render: () => {},
|
|
299
|
+
name: 'Only Plugin',
|
|
300
|
+
defaultOpen: false,
|
|
301
|
+
},
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
const state = getExistingStateFromStorage(undefined, plugins)
|
|
305
|
+
// Single plugin should be activated regardless of defaultOpen flag
|
|
306
|
+
expect(state.state.activePlugins).toEqual(['only-plugin'])
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('should merge config settings into the returned state', () => {
|
|
310
|
+
const plugins: Array<TanStackDevtoolsPlugin> = [
|
|
311
|
+
{
|
|
312
|
+
id: 'plugin1',
|
|
313
|
+
render: () => {},
|
|
314
|
+
name: 'Plugin 1',
|
|
315
|
+
},
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
const config = {
|
|
319
|
+
theme: 'light' as const,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const state = getExistingStateFromStorage(config as any, plugins)
|
|
323
|
+
expect(state.settings.theme).toBe('light')
|
|
324
|
+
expect(state.state.activePlugins).toEqual(['plugin1'])
|
|
325
|
+
})
|
|
59
326
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createContext, createEffect } from 'solid-js'
|
|
2
2
|
import { createStore } from 'solid-js/store'
|
|
3
|
+
import { getDefaultActivePlugins } from '../utils/get-default-active-plugins'
|
|
3
4
|
import { tryParseJson } from '../utils/sanitize'
|
|
4
5
|
import {
|
|
5
6
|
TANSTACK_DEVTOOLS_SETTINGS,
|
|
@@ -49,6 +50,12 @@ export interface TanStackDevtoolsPlugin {
|
|
|
49
50
|
* If not provided, it will be generated based on the name.
|
|
50
51
|
*/
|
|
51
52
|
id?: string
|
|
53
|
+
/**
|
|
54
|
+
* Whether the plugin should be open by default when there are no active plugins.
|
|
55
|
+
* If true, this plugin will be added to activePlugins on initial load when activePlugins is empty.
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
defaultOpen?: boolean
|
|
52
59
|
/**
|
|
53
60
|
* Render the plugin UI by using the provided element. This function will be called
|
|
54
61
|
* when the plugin tab is clicked and expected to be mounted.
|
|
@@ -127,26 +134,39 @@ export function getStateFromLocalStorage(
|
|
|
127
134
|
return existingState
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
const getExistingStateFromStorage = (
|
|
137
|
+
export const getExistingStateFromStorage = (
|
|
131
138
|
config?: TanStackDevtoolsConfig,
|
|
132
139
|
plugins?: Array<TanStackDevtoolsPlugin>,
|
|
133
140
|
) => {
|
|
134
141
|
const existingState = getStateFromLocalStorage(plugins)
|
|
135
142
|
const settings = getSettings()
|
|
136
143
|
|
|
144
|
+
const pluginsWithIds =
|
|
145
|
+
plugins?.map((plugin, i) => {
|
|
146
|
+
const id = generatePluginId(plugin, i)
|
|
147
|
+
return {
|
|
148
|
+
...plugin,
|
|
149
|
+
id,
|
|
150
|
+
}
|
|
151
|
+
}) || []
|
|
152
|
+
|
|
153
|
+
// If no active plugins exist, add plugins with defaultOpen: true
|
|
154
|
+
// Or if there's only 1 plugin, activate it automatically
|
|
155
|
+
let activePlugins = existingState?.activePlugins || []
|
|
156
|
+
|
|
157
|
+
const shouldFillWithDefaultOpenPlugins =
|
|
158
|
+
activePlugins.length === 0 && pluginsWithIds.length > 0
|
|
159
|
+
if (shouldFillWithDefaultOpenPlugins) {
|
|
160
|
+
activePlugins = getDefaultActivePlugins(pluginsWithIds)
|
|
161
|
+
}
|
|
162
|
+
|
|
137
163
|
const state: DevtoolsStore = {
|
|
138
164
|
...initialState,
|
|
139
|
-
plugins:
|
|
140
|
-
plugins?.map((plugin, i) => {
|
|
141
|
-
const id = generatePluginId(plugin, i)
|
|
142
|
-
return {
|
|
143
|
-
...plugin,
|
|
144
|
-
id,
|
|
145
|
-
}
|
|
146
|
-
}) || [],
|
|
165
|
+
plugins: pluginsWithIds,
|
|
147
166
|
state: {
|
|
148
167
|
...initialState.state,
|
|
149
168
|
...existingState,
|
|
169
|
+
activePlugins,
|
|
150
170
|
},
|
|
151
171
|
settings: {
|
|
152
172
|
...initialState.settings,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createEffect, createMemo, useContext } from 'solid-js'
|
|
2
|
+
import { MAX_ACTIVE_PLUGINS } from '../utils/constants.js'
|
|
2
3
|
import { DevtoolsContext } from './devtools-context.jsx'
|
|
3
4
|
import { useDrawContext } from './draw-context.jsx'
|
|
4
5
|
|
|
@@ -50,7 +51,7 @@ export const usePlugins = () => {
|
|
|
50
51
|
const updatedPlugins = isActive
|
|
51
52
|
? prev.state.activePlugins.filter((id) => id !== pluginId)
|
|
52
53
|
: [...prev.state.activePlugins, pluginId]
|
|
53
|
-
if (updatedPlugins.length >
|
|
54
|
+
if (updatedPlugins.length > MAX_ACTIVE_PLUGINS) return prev
|
|
54
55
|
return {
|
|
55
56
|
...prev,
|
|
56
57
|
state: {
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { getDefaultActivePlugins } from './get-default-active-plugins'
|
|
3
|
+
import type { PluginWithId } from './get-default-active-plugins'
|
|
4
|
+
|
|
5
|
+
describe('getDefaultActivePlugins', () => {
|
|
6
|
+
it('should return empty array when no plugins provided', () => {
|
|
7
|
+
const result = getDefaultActivePlugins([])
|
|
8
|
+
expect(result).toEqual([])
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should automatically activate a single plugin', () => {
|
|
12
|
+
const plugins: Array<PluginWithId> = [
|
|
13
|
+
{
|
|
14
|
+
id: 'only-plugin',
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const result = getDefaultActivePlugins(plugins)
|
|
19
|
+
expect(result).toEqual(['only-plugin'])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should automatically activate a single plugin even if defaultOpen is false', () => {
|
|
23
|
+
const plugins: Array<PluginWithId> = [
|
|
24
|
+
{
|
|
25
|
+
id: 'only-plugin',
|
|
26
|
+
defaultOpen: false,
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const result = getDefaultActivePlugins(plugins)
|
|
31
|
+
expect(result).toEqual(['only-plugin'])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should return empty array when multiple plugins without defaultOpen', () => {
|
|
35
|
+
const plugins: Array<PluginWithId> = [
|
|
36
|
+
{
|
|
37
|
+
id: 'plugin1',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'plugin2',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'plugin3',
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
const result = getDefaultActivePlugins(plugins)
|
|
48
|
+
expect(result).toEqual([])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should activate plugins with defaultOpen: true', () => {
|
|
52
|
+
const plugins: Array<PluginWithId> = [
|
|
53
|
+
{
|
|
54
|
+
id: 'plugin1',
|
|
55
|
+
defaultOpen: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'plugin2',
|
|
59
|
+
defaultOpen: false,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'plugin3',
|
|
63
|
+
defaultOpen: true,
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const result = getDefaultActivePlugins(plugins)
|
|
68
|
+
expect(result).toEqual(['plugin1', 'plugin3'])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should limit defaultOpen plugins to MAX_ACTIVE_PLUGINS (3)', () => {
|
|
72
|
+
const plugins: Array<PluginWithId> = [
|
|
73
|
+
{
|
|
74
|
+
id: 'plugin1',
|
|
75
|
+
defaultOpen: true,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'plugin2',
|
|
79
|
+
defaultOpen: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'plugin3',
|
|
83
|
+
defaultOpen: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'plugin4',
|
|
87
|
+
defaultOpen: true,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'plugin5',
|
|
91
|
+
defaultOpen: true,
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const result = getDefaultActivePlugins(plugins)
|
|
96
|
+
// Should only return first 3
|
|
97
|
+
expect(result).toEqual(['plugin1', 'plugin2', 'plugin3'])
|
|
98
|
+
expect(result.length).toBe(3)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should activate exactly MAX_ACTIVE_PLUGINS when that many have defaultOpen', () => {
|
|
102
|
+
const plugins: Array<PluginWithId> = [
|
|
103
|
+
{
|
|
104
|
+
id: 'plugin1',
|
|
105
|
+
defaultOpen: true,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'plugin2',
|
|
109
|
+
defaultOpen: true,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'plugin3',
|
|
113
|
+
defaultOpen: true,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'plugin4',
|
|
117
|
+
defaultOpen: false,
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
const result = getDefaultActivePlugins(plugins)
|
|
122
|
+
expect(result).toEqual(['plugin1', 'plugin2', 'plugin3'])
|
|
123
|
+
expect(result.length).toBe(3)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should handle mix of defaultOpen true/false/undefined', () => {
|
|
127
|
+
const plugins: Array<PluginWithId> = [
|
|
128
|
+
{
|
|
129
|
+
id: 'plugin1',
|
|
130
|
+
defaultOpen: true,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'plugin2',
|
|
134
|
+
// undefined defaultOpen
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'plugin3',
|
|
138
|
+
defaultOpen: false,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'plugin4',
|
|
142
|
+
defaultOpen: true,
|
|
143
|
+
},
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
const result = getDefaultActivePlugins(plugins)
|
|
147
|
+
// Only plugin1 and plugin4 have defaultOpen: true
|
|
148
|
+
expect(result).toEqual(['plugin1', 'plugin4'])
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should return single plugin even if it has defaultOpen: true', () => {
|
|
152
|
+
const plugins: Array<PluginWithId> = [
|
|
153
|
+
{
|
|
154
|
+
id: 'only-plugin',
|
|
155
|
+
defaultOpen: true,
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
const result = getDefaultActivePlugins(plugins)
|
|
160
|
+
expect(result).toEqual(['only-plugin'])
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should stop at MAX_ACTIVE_PLUGINS limit when 5 plugins have defaultOpen: true', () => {
|
|
164
|
+
const plugins: Array<PluginWithId> = [
|
|
165
|
+
{
|
|
166
|
+
id: 'plugin1',
|
|
167
|
+
defaultOpen: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'plugin2',
|
|
171
|
+
defaultOpen: true,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'plugin3',
|
|
175
|
+
defaultOpen: true,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'plugin4',
|
|
179
|
+
defaultOpen: true,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'plugin5',
|
|
183
|
+
defaultOpen: true,
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
const result = getDefaultActivePlugins(plugins)
|
|
188
|
+
// Should only activate the first 3, plugin4 and plugin5 should be ignored
|
|
189
|
+
expect(result).toEqual(['plugin1', 'plugin2', 'plugin3'])
|
|
190
|
+
expect(result.length).toBe(3)
|
|
191
|
+
expect(result).not.toContain('plugin4')
|
|
192
|
+
expect(result).not.toContain('plugin5')
|
|
193
|
+
})
|
|
194
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MAX_ACTIVE_PLUGINS } from './constants'
|
|
2
|
+
|
|
3
|
+
export interface PluginWithId {
|
|
4
|
+
id: string
|
|
5
|
+
defaultOpen?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Determines which plugins should be active by default when no plugins are currently active.
|
|
10
|
+
*
|
|
11
|
+
* Rules:
|
|
12
|
+
* 1. If there's only 1 plugin, activate it automatically
|
|
13
|
+
* 2. If there are multiple plugins, activate those with defaultOpen: true (up to MAX_ACTIVE_PLUGINS limit)
|
|
14
|
+
* 3. If no plugins have defaultOpen: true, return empty array
|
|
15
|
+
*
|
|
16
|
+
* @param plugins - Array of plugins with IDs
|
|
17
|
+
* @returns Array of plugin IDs that should be active by default
|
|
18
|
+
*/
|
|
19
|
+
export function getDefaultActivePlugins(
|
|
20
|
+
plugins: Array<PluginWithId>,
|
|
21
|
+
): Array<string> {
|
|
22
|
+
if (plugins.length === 0) {
|
|
23
|
+
return []
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If there's only 1 plugin, activate it automatically
|
|
27
|
+
if (plugins.length === 1) {
|
|
28
|
+
return [plugins[0]!.id]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Otherwise, activate plugins with defaultOpen: true (up to the limit)
|
|
32
|
+
return plugins
|
|
33
|
+
.filter((plugin) => plugin.defaultOpen === true)
|
|
34
|
+
.slice(0, MAX_ACTIVE_PLUGINS)
|
|
35
|
+
.map((plugin) => plugin.id)
|
|
36
|
+
}
|