New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@tanstack/devtools

Package Overview
Dependencies
Maintainers
2
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/devtools - npm Package Compare versions

Comparing version
0.6.24
to
0.7.0
+303
dist/chunk/VZEY7HNC.js
import { createComponent, delegateEvents } from 'solid-js/web';
import { createContext, createSignal, createEffect, onCleanup, createMemo, useContext } from 'solid-js';
import { createStore } from 'solid-js/store';
// src/constants.ts
var PLUGIN_CONTAINER_ID = "plugin-container";
var PLUGIN_TITLE_CONTAINER_ID = "plugin-title-container";
// src/utils/sanitize.ts
var tryParseJson = (json) => {
if (!json) return void 0;
try {
return JSON.parse(json);
} catch (_e) {
return void 0;
}
};
var uppercaseFirstLetter = (value) => value.charAt(0).toUpperCase() + value.slice(1);
var getAllPermutations = (arr) => {
const res = [];
function permutate(arr2, start) {
if (start === arr2.length - 1) {
res.push([...arr2]);
return;
}
for (let i = start; i < arr2.length; i++) {
[arr2[start], arr2[i]] = [arr2[i], arr2[start]];
permutate(arr2, start + 1);
[arr2[start], arr2[i]] = [arr2[i], arr2[start]];
}
}
permutate(arr, 0);
return res;
};
// src/utils/storage.ts
var getStorageItem = (key) => localStorage.getItem(key);
var setStorageItem = (key, value) => {
try {
localStorage.setItem(key, value);
} catch (_e) {
return;
}
};
var TANSTACK_DEVTOOLS = "tanstack_devtools";
var TANSTACK_DEVTOOLS_STATE = "tanstack_devtools_state";
var TANSTACK_DEVTOOLS_SETTINGS = "tanstack_devtools_settings";
// src/context/devtools-store.ts
var keyboardModifiers = [
"Alt",
"Control",
"Meta",
"Shift"
];
var initialState = {
settings: {
defaultOpen: false,
hideUntilHover: false,
position: "bottom-right",
panelLocation: "bottom",
openHotkey: ["Shift", "A"],
requireUrlFlag: false,
urlFlag: "tanstack-devtools",
theme: typeof window !== "undefined" && typeof window.matchMedia !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
triggerImage: "",
triggerHidden: false
},
state: {
activeTab: "plugins",
height: 400,
activePlugins: [],
persistOpen: false
}
};
var PiPContext = createContext(void 0);
var PiPProvider = (props) => {
const [pipWindow, setPipWindow] = createSignal(null);
const closePipWindow = () => {
const w = pipWindow();
if (w != null) {
w.close();
setPipWindow(null);
}
};
const requestPipWindow = (settings) => {
if (pipWindow() != null) {
return;
}
const pip = window.open("", "TSDT-Devtools-Panel", `${settings},popup`);
if (!pip) {
throw new Error("Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.");
}
import.meta.hot?.on("vite:beforeUpdate", () => {
localStorage.setItem("pip_open", "false");
closePipWindow();
});
window.addEventListener("beforeunload", () => {
localStorage.setItem("pip_open", "false");
closePipWindow();
});
pip.document.head.innerHTML = "";
pip.document.body.innerHTML = "";
pip.document.title = "TanStack Devtools";
pip.document.body.style.margin = "0";
pip.addEventListener("pagehide", () => {
localStorage.setItem("pip_open", "false");
closePipWindow();
});
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join("");
const style = document.createElement("style");
const style_node = styleSheet.ownerNode;
let style_id = "";
if (style_node && "id" in style_node) {
style_id = style_node.id;
}
if (style_id) {
style.setAttribute("id", style_id);
}
style.textContent = cssRules;
pip.document.head.appendChild(style);
} catch (e) {
const link = document.createElement("link");
if (styleSheet.href == null) {
return;
}
link.rel = "stylesheet";
link.type = styleSheet.type;
link.media = styleSheet.media.toString();
link.href = styleSheet.href;
pip.document.head.appendChild(link);
}
});
delegateEvents(["focusin", "focusout", "pointermove", "keydown", "pointerdown", "pointerup", "click", "mousedown", "input"], pip.document);
setPipWindow(pip);
};
createEffect(() => {
const gooberStyles = document.querySelector("#_goober");
const w = pipWindow();
if (gooberStyles && w) {
const observer = new MutationObserver(() => {
const pip_style = w.document.querySelector("#_goober");
if (pip_style) {
pip_style.textContent = gooberStyles.textContent;
}
});
observer.observe(gooberStyles, {
childList: true,
// observe direct children
subtree: true,
// and lower descendants too
characterDataOldValue: true
// pass old data to callback
});
onCleanup(() => {
observer.disconnect();
});
}
});
const value = createMemo(() => ({
pipWindow: pipWindow(),
requestPipWindow,
closePipWindow,
disabled: props.disabled ?? false
}));
return createComponent(PiPContext.Provider, {
value,
get children() {
return props.children;
}
});
};
var usePiPWindow = () => {
const context = createMemo(() => {
const ctx = useContext(PiPContext);
if (!ctx) {
throw new Error("usePiPWindow must be used within a PiPProvider");
}
return ctx();
});
return context;
};
// src/utils/constants.ts
var MAX_ACTIVE_PLUGINS = 3;
// src/utils/get-default-active-plugins.ts
function getDefaultActivePlugins(plugins) {
if (plugins.length === 0) {
return [];
}
if (plugins.length === 1) {
return [plugins[0].id];
}
return plugins.filter((plugin) => plugin.defaultOpen === true).slice(0, MAX_ACTIVE_PLUGINS).map((plugin) => plugin.id);
}
// src/context/devtools-context.tsx
var DevtoolsContext = createContext();
var getSettings = () => {
const settingsString = getStorageItem(TANSTACK_DEVTOOLS_SETTINGS);
const settings = tryParseJson(settingsString);
return {
...settings
};
};
var generatePluginId = (plugin, index) => {
if (plugin.id) {
return plugin.id;
}
if (typeof plugin.name === "string") {
return `${plugin.name.toLowerCase().replace(" ", "-")}-${index}`;
}
return index.toString();
};
function getStateFromLocalStorage(plugins) {
const existingStateString = getStorageItem(TANSTACK_DEVTOOLS_STATE);
const existingState = tryParseJson(existingStateString);
const pluginIds = plugins?.map((plugin, i) => generatePluginId(plugin, i)) || [];
if (existingState?.activePlugins) {
const originalLength = existingState.activePlugins.length;
existingState.activePlugins = existingState.activePlugins.filter((id) => pluginIds.includes(id));
if (existingState.activePlugins.length !== originalLength) {
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(existingState));
}
}
return existingState;
}
var getExistingStateFromStorage = (config, plugins) => {
const existingState = getStateFromLocalStorage(plugins);
const settings = getSettings();
const pluginsWithIds = plugins?.map((plugin, i) => {
const id = generatePluginId(plugin, i);
return {
...plugin,
id
};
}) || [];
let activePlugins = existingState?.activePlugins || [];
const shouldFillWithDefaultOpenPlugins = activePlugins.length === 0 && pluginsWithIds.length > 0;
if (shouldFillWithDefaultOpenPlugins) {
activePlugins = getDefaultActivePlugins(pluginsWithIds);
}
const state = {
...initialState,
plugins: pluginsWithIds,
state: {
...initialState.state,
...existingState,
activePlugins
},
settings: {
...initialState.settings,
...config,
...settings
}
};
return state;
};
var DevtoolsProvider = (props) => {
const [store, setStore] = createStore(getExistingStateFromStorage(props.config, props.plugins));
const updatePlugins = (newPlugins) => {
const pluginsWithIds = newPlugins.map((plugin, i) => {
const id = generatePluginId(plugin, i);
return {
...plugin,
id
};
});
setStore("plugins", pluginsWithIds);
};
createEffect(() => {
if (props.onSetPlugins) {
props.onSetPlugins(updatePlugins);
}
});
const value = {
store,
setStore: (updater) => {
const newState = updater(store);
const {
settings,
state: internalState
} = newState;
setStorageItem(TANSTACK_DEVTOOLS_SETTINGS, JSON.stringify(settings));
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(internalState));
setStore((prev) => ({
...prev,
...newState
}));
}
};
return createComponent(DevtoolsContext.Provider, {
value,
get children() {
return props.children;
}
});
};
export { DevtoolsContext, DevtoolsProvider, MAX_ACTIVE_PLUGINS, PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID, PiPProvider, TANSTACK_DEVTOOLS, getAllPermutations, initialState, keyboardModifiers, uppercaseFirstLetter, usePiPWindow };

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

/**
* Maximum number of plugins that can be active simultaneously in the devtools
*/
export const MAX_ACTIVE_PLUGINS = 3
import { describe, expect, it } from 'vitest'
import { getDefaultActivePlugins } from './get-default-active-plugins'
import type { PluginWithId } from './get-default-active-plugins'
describe('getDefaultActivePlugins', () => {
it('should return empty array when no plugins provided', () => {
const result = getDefaultActivePlugins([])
expect(result).toEqual([])
})
it('should automatically activate a single plugin', () => {
const plugins: Array<PluginWithId> = [
{
id: 'only-plugin',
},
]
const result = getDefaultActivePlugins(plugins)
expect(result).toEqual(['only-plugin'])
})
it('should automatically activate a single plugin even if defaultOpen is false', () => {
const plugins: Array<PluginWithId> = [
{
id: 'only-plugin',
defaultOpen: false,
},
]
const result = getDefaultActivePlugins(plugins)
expect(result).toEqual(['only-plugin'])
})
it('should return empty array when multiple plugins without defaultOpen', () => {
const plugins: Array<PluginWithId> = [
{
id: 'plugin1',
},
{
id: 'plugin2',
},
{
id: 'plugin3',
},
]
const result = getDefaultActivePlugins(plugins)
expect(result).toEqual([])
})
it('should activate plugins with defaultOpen: true', () => {
const plugins: Array<PluginWithId> = [
{
id: 'plugin1',
defaultOpen: true,
},
{
id: 'plugin2',
defaultOpen: false,
},
{
id: 'plugin3',
defaultOpen: true,
},
]
const result = getDefaultActivePlugins(plugins)
expect(result).toEqual(['plugin1', 'plugin3'])
})
it('should limit defaultOpen plugins to MAX_ACTIVE_PLUGINS (3)', () => {
const plugins: Array<PluginWithId> = [
{
id: 'plugin1',
defaultOpen: true,
},
{
id: 'plugin2',
defaultOpen: true,
},
{
id: 'plugin3',
defaultOpen: true,
},
{
id: 'plugin4',
defaultOpen: true,
},
{
id: 'plugin5',
defaultOpen: true,
},
]
const result = getDefaultActivePlugins(plugins)
// Should only return first 3
expect(result).toEqual(['plugin1', 'plugin2', 'plugin3'])
expect(result.length).toBe(3)
})
it('should activate exactly MAX_ACTIVE_PLUGINS when that many have defaultOpen', () => {
const plugins: Array<PluginWithId> = [
{
id: 'plugin1',
defaultOpen: true,
},
{
id: 'plugin2',
defaultOpen: true,
},
{
id: 'plugin3',
defaultOpen: true,
},
{
id: 'plugin4',
defaultOpen: false,
},
]
const result = getDefaultActivePlugins(plugins)
expect(result).toEqual(['plugin1', 'plugin2', 'plugin3'])
expect(result.length).toBe(3)
})
it('should handle mix of defaultOpen true/false/undefined', () => {
const plugins: Array<PluginWithId> = [
{
id: 'plugin1',
defaultOpen: true,
},
{
id: 'plugin2',
// undefined defaultOpen
},
{
id: 'plugin3',
defaultOpen: false,
},
{
id: 'plugin4',
defaultOpen: true,
},
]
const result = getDefaultActivePlugins(plugins)
// Only plugin1 and plugin4 have defaultOpen: true
expect(result).toEqual(['plugin1', 'plugin4'])
})
it('should return single plugin even if it has defaultOpen: true', () => {
const plugins: Array<PluginWithId> = [
{
id: 'only-plugin',
defaultOpen: true,
},
]
const result = getDefaultActivePlugins(plugins)
expect(result).toEqual(['only-plugin'])
})
it('should stop at MAX_ACTIVE_PLUGINS limit when 5 plugins have defaultOpen: true', () => {
const plugins: Array<PluginWithId> = [
{
id: 'plugin1',
defaultOpen: true,
},
{
id: 'plugin2',
defaultOpen: true,
},
{
id: 'plugin3',
defaultOpen: true,
},
{
id: 'plugin4',
defaultOpen: true,
},
{
id: 'plugin5',
defaultOpen: true,
},
]
const result = getDefaultActivePlugins(plugins)
// Should only activate the first 3, plugin4 and plugin5 should be ignored
expect(result).toEqual(['plugin1', 'plugin2', 'plugin3'])
expect(result.length).toBe(3)
expect(result).not.toContain('plugin4')
expect(result).not.toContain('plugin5')
})
})
import { MAX_ACTIVE_PLUGINS } from './constants'
export interface PluginWithId {
id: string
defaultOpen?: boolean
}
/**
* Determines which plugins should be active by default when no plugins are currently active.
*
* Rules:
* 1. If there's only 1 plugin, activate it automatically
* 2. If there are multiple plugins, activate those with defaultOpen: true (up to MAX_ACTIVE_PLUGINS limit)
* 3. If no plugins have defaultOpen: true, return empty array
*
* @param plugins - Array of plugins with IDs
* @returns Array of plugin IDs that should be active by default
*/
export function getDefaultActivePlugins(
plugins: Array<PluginWithId>,
): Array<string> {
if (plugins.length === 0) {
return []
}
// If there's only 1 plugin, activate it automatically
if (plugins.length === 1) {
return [plugins[0]!.id]
}
// Otherwise, activate plugins with defaultOpen: true (up to the limit)
return plugins
.filter((plugin) => plugin.defaultOpen === true)
.slice(0, MAX_ACTIVE_PLUGINS)
.map((plugin) => plugin.id)
}
+3
-3

@@ -1,3 +0,3 @@

import { initialState, DevtoolsProvider, PiPProvider } from './chunk/XF4JFOLU.js';
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/XF4JFOLU.js';
import { initialState, DevtoolsProvider, PiPProvider } from './chunk/VZEY7HNC.js';
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/VZEY7HNC.js';
import { render, createComponent, Portal } from 'solid-js/web';

@@ -33,3 +33,3 @@ import { lazy } from 'solid-js';

const _self$ = this;
this.#Component = lazy(() => import('./devtools/UUNAZSBD.js'));
this.#Component = lazy(() => import('./devtools/7NDEDZB7.js'));
const Devtools = this.#Component;

@@ -36,0 +36,0 @@ this.#eventBus = new ClientEventBus(this.#eventBusConfig);

@@ -124,2 +124,8 @@ import { ClientEventBusConfig } from '@tanstack/devtools-event-bus/client';

/**
* Whether the plugin should be open by default when there are no active plugins.
* If true, this plugin will be added to activePlugins on initial load when activePlugins is empty.
* @default false
*/
defaultOpen?: boolean;
/**
* Render the plugin UI by using the provided element. This function will be called

@@ -126,0 +132,0 @@ * when the plugin tab is clicked and expected to be mounted.

@@ -1,3 +0,3 @@

import { initialState, DevtoolsProvider, PiPProvider } from './chunk/XF4JFOLU.js';
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/XF4JFOLU.js';
import { initialState, DevtoolsProvider, PiPProvider } from './chunk/VZEY7HNC.js';
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/VZEY7HNC.js';
import { render, createComponent, Portal } from 'solid-js/web';

@@ -33,3 +33,3 @@ import { lazy } from 'solid-js';

const _self$ = this;
this.#Component = lazy(() => import('./devtools/UUNAZSBD.js'));
this.#Component = lazy(() => import('./devtools/7NDEDZB7.js'));
const Devtools = this.#Component;

@@ -36,0 +36,0 @@ this.#eventBus = new ClientEventBus(this.#eventBusConfig);

@@ -1,3 +0,3 @@

import { initialState } from './chunk/XF4JFOLU.js';
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/XF4JFOLU.js';
import { initialState } from './chunk/VZEY7HNC.js';
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/VZEY7HNC.js';
import 'solid-js/web';

@@ -4,0 +4,0 @@ import 'solid-js';

{
"name": "@tanstack/devtools",
"version": "0.6.24",
"version": "0.7.0",
"description": "TanStack Devtools is a set of tools for building advanced devtools for your application.",

@@ -59,4 +59,4 @@ "author": "Tanner Linsley",

"@tanstack/devtools-client": "0.0.3",
"@tanstack/devtools-event-bus": "0.3.3",
"@tanstack/devtools-ui": "0.4.4"
"@tanstack/devtools-ui": "0.4.4",
"@tanstack/devtools-event-bus": "0.3.3"
},

@@ -63,0 +63,0 @@ "peerDependencies": {

import { beforeEach, describe, expect, it } from 'vitest'
import { TANSTACK_DEVTOOLS_STATE } from '../utils/storage'
import { getStateFromLocalStorage } from './devtools-context'
import {
getExistingStateFromStorage,
getStateFromLocalStorage,
} from './devtools-context'
import type { TanStackDevtoolsPlugin } from './devtools-context'

@@ -59,2 +63,265 @@ describe('getStateFromLocalStorage', () => {

})
it('should return undefined when no localStorage state exists (allowing defaultOpen to be applied)', () => {
// No existing state in localStorage - this allows defaultOpen logic to trigger
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
defaultOpen: true,
},
{
id: 'plugin2',
render: () => {},
name: 'Plugin 2',
defaultOpen: false,
},
{
id: 'plugin3',
render: () => {},
name: 'Plugin 3',
defaultOpen: true,
},
]
// When undefined is returned, getExistingStateFromStorage will fill activePlugins with defaultOpen plugins
const state = getStateFromLocalStorage(plugins)
expect(state).toEqual(undefined)
})
it('should preserve existing activePlugins from localStorage (defaultOpen should not override)', () => {
const mockState = {
activePlugins: ['plugin2'],
settings: {
theme: 'dark',
},
}
localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState))
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
defaultOpen: true,
},
{
id: 'plugin2',
render: () => {},
name: 'Plugin 2',
defaultOpen: false,
},
]
const state = getStateFromLocalStorage(plugins)
// Should keep existing activePlugins - defaultOpen logic won't override in getExistingStateFromStorage
expect(state?.activePlugins).toEqual(['plugin2'])
})
it('should automatically activate a single plugin when no active plugins exist', () => {
// No existing state in localStorage
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'only-plugin',
render: () => {},
name: 'Only Plugin',
},
]
const state = getStateFromLocalStorage(plugins)
// Should return undefined - the single plugin activation happens in getExistingStateFromStorage
expect(state).toEqual(undefined)
})
})
describe('getExistingStateFromStorage - integration tests', () => {
beforeEach(() => {
localStorage.clear()
})
it('should automatically activate a single plugin when no localStorage state exists', () => {
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'only-plugin',
render: () => {},
name: 'Only Plugin',
},
]
const state = getExistingStateFromStorage(undefined, plugins)
expect(state.state.activePlugins).toEqual(['only-plugin'])
expect(state.plugins).toHaveLength(1)
expect(state.plugins![0]?.id).toBe('only-plugin')
})
it('should activate plugins with defaultOpen: true when no localStorage state exists', () => {
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
defaultOpen: true,
},
{
id: 'plugin2',
render: () => {},
name: 'Plugin 2',
defaultOpen: false,
},
{
id: 'plugin3',
render: () => {},
name: 'Plugin 3',
defaultOpen: true,
},
]
const state = getExistingStateFromStorage(undefined, plugins)
expect(state.state.activePlugins).toEqual(['plugin1', 'plugin3'])
expect(state.plugins).toHaveLength(3)
})
it('should limit defaultOpen plugins to MAX_ACTIVE_PLUGINS (3) when 5 have defaultOpen: true', () => {
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
defaultOpen: true,
},
{
id: 'plugin2',
render: () => {},
name: 'Plugin 2',
defaultOpen: true,
},
{
id: 'plugin3',
render: () => {},
name: 'Plugin 3',
defaultOpen: true,
},
{
id: 'plugin4',
render: () => {},
name: 'Plugin 4',
defaultOpen: true,
},
{
id: 'plugin5',
render: () => {},
name: 'Plugin 5',
defaultOpen: true,
},
]
const state = getExistingStateFromStorage(undefined, plugins)
// Should only activate first 3 plugins
expect(state.state.activePlugins).toEqual(['plugin1', 'plugin2', 'plugin3'])
expect(state.state.activePlugins).toHaveLength(3)
expect(state.state.activePlugins).not.toContain('plugin4')
expect(state.state.activePlugins).not.toContain('plugin5')
// All 5 plugins should still be in the plugins array
expect(state.plugins).toHaveLength(5)
})
it('should preserve existing activePlugins from localStorage even when plugins have defaultOpen', () => {
const mockState = {
activePlugins: ['plugin2', 'plugin4'],
settings: {
theme: 'dark',
},
}
localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState))
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
defaultOpen: true,
},
{
id: 'plugin2',
render: () => {},
name: 'Plugin 2',
defaultOpen: false,
},
{
id: 'plugin3',
render: () => {},
name: 'Plugin 3',
defaultOpen: true,
},
{
id: 'plugin4',
render: () => {},
name: 'Plugin 4',
defaultOpen: false,
},
]
const state = getExistingStateFromStorage(undefined, plugins)
// Should preserve the localStorage state, not use defaultOpen
expect(state.state.activePlugins).toEqual(['plugin2', 'plugin4'])
expect(state.plugins).toHaveLength(4)
})
it('should return empty activePlugins when no defaultOpen and multiple plugins', () => {
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
},
{
id: 'plugin2',
render: () => {},
name: 'Plugin 2',
},
{
id: 'plugin3',
render: () => {},
name: 'Plugin 3',
},
]
const state = getExistingStateFromStorage(undefined, plugins)
expect(state.state.activePlugins).toEqual([])
expect(state.plugins).toHaveLength(3)
})
it('should handle single plugin with defaultOpen: false by activating it anyway', () => {
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'only-plugin',
render: () => {},
name: 'Only Plugin',
defaultOpen: false,
},
]
const state = getExistingStateFromStorage(undefined, plugins)
// Single plugin should be activated regardless of defaultOpen flag
expect(state.state.activePlugins).toEqual(['only-plugin'])
})
it('should merge config settings into the returned state', () => {
const plugins: Array<TanStackDevtoolsPlugin> = [
{
id: 'plugin1',
render: () => {},
name: 'Plugin 1',
},
]
const config = {
theme: 'light' as const,
}
const state = getExistingStateFromStorage(config as any, plugins)
expect(state.settings.theme).toBe('light')
expect(state.state.activePlugins).toEqual(['plugin1'])
})
})
import { createContext, createEffect } from 'solid-js'
import { createStore } from 'solid-js/store'
import { getDefaultActivePlugins } from '../utils/get-default-active-plugins'
import { tryParseJson } from '../utils/sanitize'

@@ -53,2 +54,8 @@ import {

/**
* Whether the plugin should be open by default when there are no active plugins.
* If true, this plugin will be added to activePlugins on initial load when activePlugins is empty.
* @default false
*/
defaultOpen?: boolean
/**
* Render the plugin UI by using the provided element. This function will be called

@@ -131,3 +138,3 @@ * when the plugin tab is clicked and expected to be mounted.

const getExistingStateFromStorage = (
export const getExistingStateFromStorage = (
config?: TanStackDevtoolsConfig,

@@ -139,15 +146,28 @@ plugins?: Array<TanStackDevtoolsPlugin>,

const pluginsWithIds =
plugins?.map((plugin, i) => {
const id = generatePluginId(plugin, i)
return {
...plugin,
id,
}
}) || []
// If no active plugins exist, add plugins with defaultOpen: true
// Or if there's only 1 plugin, activate it automatically
let activePlugins = existingState?.activePlugins || []
const shouldFillWithDefaultOpenPlugins =
activePlugins.length === 0 && pluginsWithIds.length > 0
if (shouldFillWithDefaultOpenPlugins) {
activePlugins = getDefaultActivePlugins(pluginsWithIds)
}
const state: DevtoolsStore = {
...initialState,
plugins:
plugins?.map((plugin, i) => {
const id = generatePluginId(plugin, i)
return {
...plugin,
id,
}
}) || [],
plugins: pluginsWithIds,
state: {
...initialState.state,
...existingState,
activePlugins,
},

@@ -154,0 +174,0 @@ settings: {

import { createEffect, createMemo, useContext } from 'solid-js'
import { MAX_ACTIVE_PLUGINS } from '../utils/constants.js'
import { DevtoolsContext } from './devtools-context.jsx'

@@ -53,3 +54,3 @@ import { useDrawContext } from './draw-context.jsx'

: [...prev.state.activePlugins, pluginId]
if (updatedPlugins.length > 3) return prev
if (updatedPlugins.length > MAX_ACTIVE_PLUGINS) return prev
return {

@@ -56,0 +57,0 @@ ...prev,

import { createComponent, delegateEvents } from 'solid-js/web';
import { createContext, createSignal, createEffect, onCleanup, createMemo, useContext } from 'solid-js';
import { createStore } from 'solid-js/store';
// src/constants.ts
var PLUGIN_CONTAINER_ID = "plugin-container";
var PLUGIN_TITLE_CONTAINER_ID = "plugin-title-container";
// src/utils/sanitize.ts
var tryParseJson = (json) => {
if (!json) return void 0;
try {
return JSON.parse(json);
} catch (_e) {
return void 0;
}
};
var uppercaseFirstLetter = (value) => value.charAt(0).toUpperCase() + value.slice(1);
var getAllPermutations = (arr) => {
const res = [];
function permutate(arr2, start) {
if (start === arr2.length - 1) {
res.push([...arr2]);
return;
}
for (let i = start; i < arr2.length; i++) {
[arr2[start], arr2[i]] = [arr2[i], arr2[start]];
permutate(arr2, start + 1);
[arr2[start], arr2[i]] = [arr2[i], arr2[start]];
}
}
permutate(arr, 0);
return res;
};
// src/utils/storage.ts
var getStorageItem = (key) => localStorage.getItem(key);
var setStorageItem = (key, value) => {
try {
localStorage.setItem(key, value);
} catch (_e) {
return;
}
};
var TANSTACK_DEVTOOLS = "tanstack_devtools";
var TANSTACK_DEVTOOLS_STATE = "tanstack_devtools_state";
var TANSTACK_DEVTOOLS_SETTINGS = "tanstack_devtools_settings";
// src/context/devtools-store.ts
var keyboardModifiers = [
"Alt",
"Control",
"Meta",
"Shift"
];
var initialState = {
settings: {
defaultOpen: false,
hideUntilHover: false,
position: "bottom-right",
panelLocation: "bottom",
openHotkey: ["Shift", "A"],
requireUrlFlag: false,
urlFlag: "tanstack-devtools",
theme: typeof window !== "undefined" && typeof window.matchMedia !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
triggerImage: "",
triggerHidden: false
},
state: {
activeTab: "plugins",
height: 400,
activePlugins: [],
persistOpen: false
}
};
var PiPContext = createContext(void 0);
var PiPProvider = (props) => {
const [pipWindow, setPipWindow] = createSignal(null);
const closePipWindow = () => {
const w = pipWindow();
if (w != null) {
w.close();
setPipWindow(null);
}
};
const requestPipWindow = (settings) => {
if (pipWindow() != null) {
return;
}
const pip = window.open("", "TSDT-Devtools-Panel", `${settings},popup`);
if (!pip) {
throw new Error("Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.");
}
import.meta.hot?.on("vite:beforeUpdate", () => {
localStorage.setItem("pip_open", "false");
closePipWindow();
});
window.addEventListener("beforeunload", () => {
localStorage.setItem("pip_open", "false");
closePipWindow();
});
pip.document.head.innerHTML = "";
pip.document.body.innerHTML = "";
pip.document.title = "TanStack Devtools";
pip.document.body.style.margin = "0";
pip.addEventListener("pagehide", () => {
localStorage.setItem("pip_open", "false");
closePipWindow();
});
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join("");
const style = document.createElement("style");
const style_node = styleSheet.ownerNode;
let style_id = "";
if (style_node && "id" in style_node) {
style_id = style_node.id;
}
if (style_id) {
style.setAttribute("id", style_id);
}
style.textContent = cssRules;
pip.document.head.appendChild(style);
} catch (e) {
const link = document.createElement("link");
if (styleSheet.href == null) {
return;
}
link.rel = "stylesheet";
link.type = styleSheet.type;
link.media = styleSheet.media.toString();
link.href = styleSheet.href;
pip.document.head.appendChild(link);
}
});
delegateEvents(["focusin", "focusout", "pointermove", "keydown", "pointerdown", "pointerup", "click", "mousedown", "input"], pip.document);
setPipWindow(pip);
};
createEffect(() => {
const gooberStyles = document.querySelector("#_goober");
const w = pipWindow();
if (gooberStyles && w) {
const observer = new MutationObserver(() => {
const pip_style = w.document.querySelector("#_goober");
if (pip_style) {
pip_style.textContent = gooberStyles.textContent;
}
});
observer.observe(gooberStyles, {
childList: true,
// observe direct children
subtree: true,
// and lower descendants too
characterDataOldValue: true
// pass old data to callback
});
onCleanup(() => {
observer.disconnect();
});
}
});
const value = createMemo(() => ({
pipWindow: pipWindow(),
requestPipWindow,
closePipWindow,
disabled: props.disabled ?? false
}));
return createComponent(PiPContext.Provider, {
value,
get children() {
return props.children;
}
});
};
var usePiPWindow = () => {
const context = createMemo(() => {
const ctx = useContext(PiPContext);
if (!ctx) {
throw new Error("usePiPWindow must be used within a PiPProvider");
}
return ctx();
});
return context;
};
var DevtoolsContext = createContext();
var getSettings = () => {
const settingsString = getStorageItem(TANSTACK_DEVTOOLS_SETTINGS);
const settings = tryParseJson(settingsString);
return {
...settings
};
};
var generatePluginId = (plugin, index) => {
if (plugin.id) {
return plugin.id;
}
if (typeof plugin.name === "string") {
return `${plugin.name.toLowerCase().replace(" ", "-")}-${index}`;
}
return index.toString();
};
function getStateFromLocalStorage(plugins) {
const existingStateString = getStorageItem(TANSTACK_DEVTOOLS_STATE);
const existingState = tryParseJson(existingStateString);
const pluginIds = plugins?.map((plugin, i) => generatePluginId(plugin, i)) || [];
if (existingState?.activePlugins) {
const originalLength = existingState.activePlugins.length;
existingState.activePlugins = existingState.activePlugins.filter((id) => pluginIds.includes(id));
if (existingState.activePlugins.length !== originalLength) {
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(existingState));
}
}
return existingState;
}
var getExistingStateFromStorage = (config, plugins) => {
const existingState = getStateFromLocalStorage(plugins);
const settings = getSettings();
const state = {
...initialState,
plugins: plugins?.map((plugin, i) => {
const id = generatePluginId(plugin, i);
return {
...plugin,
id
};
}) || [],
state: {
...initialState.state,
...existingState
},
settings: {
...initialState.settings,
...config,
...settings
}
};
return state;
};
var DevtoolsProvider = (props) => {
const [store, setStore] = createStore(getExistingStateFromStorage(props.config, props.plugins));
const updatePlugins = (newPlugins) => {
const pluginsWithIds = newPlugins.map((plugin, i) => {
const id = generatePluginId(plugin, i);
return {
...plugin,
id
};
});
setStore("plugins", pluginsWithIds);
};
createEffect(() => {
if (props.onSetPlugins) {
props.onSetPlugins(updatePlugins);
}
});
const value = {
store,
setStore: (updater) => {
const newState = updater(store);
const {
settings,
state: internalState
} = newState;
setStorageItem(TANSTACK_DEVTOOLS_SETTINGS, JSON.stringify(settings));
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(internalState));
setStore((prev) => ({
...prev,
...newState
}));
}
};
return createComponent(DevtoolsContext.Provider, {
value,
get children() {
return props.children;
}
});
};
export { DevtoolsContext, DevtoolsProvider, PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID, PiPProvider, TANSTACK_DEVTOOLS, getAllPermutations, initialState, keyboardModifiers, uppercaseFirstLetter, usePiPWindow };

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display