New:Socket for Asana Is Now Available.Learn more
Get Started

@tanstack/devtools

Package Overview
Dependencies
Maintainers
3
Versions
73
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.14.0
to
0.14.1
+35
dist/chunk/GZI3DACQ.js
// src/context/devtools-store.ts
var keyboardModifiers = [
"Alt",
"Control",
"Meta",
"Shift",
"CtrlOrMeta"
];
var initialState = {
settings: {
defaultOpen: false,
hideUntilHover: false,
position: "bottom-right",
triggerMode: "floating",
triggerCoords: void 0,
panelLocation: "bottom",
openHotkey: ["Control", "~"],
inspectHotkey: ["Shift", "Alt", "CtrlOrMeta"],
requireUrlFlag: false,
urlFlag: "tanstack-devtools",
theme: typeof window !== "undefined" && typeof window.matchMedia !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
sourceAction: "ide-warp",
triggerHidden: false,
customTrigger: void 0
},
state: {
activeTab: "plugins",
height: 400,
layout: null,
persistOpen: false,
subheaderCollapsed: false
}
};
export { initialState, keyboardModifiers };
import { initialState } from './GZI3DACQ.js';
import { createContext, createEffect, createComponent, createSignal, onCleanup, createMemo, useContext } from 'solid-js';
import { createStore, reconcile } from 'solid-js/store';
import { createComponent as createComponent$1, delegateEvents } from 'solid-js/web';
import { ensureDevtoolsStyles } from '@tanstack/devtools-ui/internal';
// src/utils/constants.ts
var MAX_ACTIVE_PLUGINS = 18;
var PLUGIN_GROUP_TAB_HEIGHT = 32;
var PANE_CARD_INSET = 8;
var PLUGIN_SPLITTER_SIZE = 8;
var MIN_PANE_SIZE = { w: 280, h: 160 };
var PANE_DROP_EDGE_RATIO = 0.25;
var WORKBENCH_HEADER_HEIGHT = 36;
var PLUGINS_STRIP_HEIGHT = 44;
var WORKBENCH_GUTTER = 16;
var WORKBENCH_GUTTER_NARROW = 12;
var PANEL_CLOSE_THRESHOLD = 70;
var PANEL_MAX_VIEWPORT_RATIO = 0.9;
// src/utils/storage.ts
var getStorageItem = (key) => {
return 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/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/utils/layout-tree.ts
var EPSILON = 1e-9;
var MAX_STORED_DEPTH = 32;
var isGroup = (node) => node.kind === "group";
var isSplit = (node) => node.kind === "split";
var flattenTabs = (tree) => tree === null ? [] : isGroup(tree) ? [...tree.tabs] : tree.children.flatMap(flattenTabs);
var allGroups = (tree) => tree === null ? [] : isGroup(tree) ? [tree] : tree.children.flatMap(allGroups);
var findGroupOfTab = (tree, tabId) => allGroups(tree).find((group) => group.tabs.includes(tabId)) ?? null;
var findGroupById = (tree, groupId) => allGroups(tree).find((group) => group.id === groupId) ?? null;
var nextGroupId = (tree) => {
let highest = -1;
for (const group of allGroups(tree)) {
const match = /^g(\d+)$/.exec(group.id);
if (match) highest = Math.max(highest, Number(match[1]));
}
return `g${highest + 1}`;
};
var singleGroup = (tabs, id = "g0") => tabs.length === 0 ? null : { kind: "group", id, tabs: [...tabs], active: 0 };
var normalise = (sizes, count) => {
const usable = sizes.length === count && sizes.every((n) => Number.isFinite(n) && n > 0) ? sizes : Array.from({ length: count }, () => 1);
const total = usable.reduce((sum, n) => sum + n, 0);
return total > EPSILON ? usable.map((n) => n / total) : Array.from({ length: count }, () => 1 / count);
};
var split = (dir, children, sizes) => ({
kind: "split",
dir,
sizes: normalise(sizes ?? [], children.length),
children
});
var prune = (node) => {
if (node === null) return null;
if (isGroup(node)) {
if (node.tabs.length === 0) return null;
const active = Math.min(Math.max(node.active, 0), node.tabs.length - 1);
return active === node.active ? node : { ...node, active };
}
const kept = [];
const keptSizes = [];
node.children.forEach((child, index) => {
const pruned = prune(child);
if (pruned === null) return;
if (isSplit(pruned) && pruned.dir === node.dir) {
const share = node.sizes[index] ?? 1 / node.children.length;
pruned.children.forEach((grandchild, inner) => {
kept.push(grandchild);
keptSizes.push(share * (pruned.sizes[inner] ?? 0));
});
return;
}
kept.push(pruned);
keptSizes.push(node.sizes[index] ?? 1 / node.children.length);
});
if (kept.length === 0) return null;
if (kept.length === 1) return kept[0];
return split(node.dir, kept, keptSizes);
};
var closeTab = (tree, tabId) => {
const strip = (node) => {
if (isGroup(node)) {
const index = node.tabs.indexOf(tabId);
if (index === -1) return node;
const tabs = node.tabs.filter((id) => id !== tabId);
const active = node.active > index ? node.active - 1 : node.active;
return { ...node, tabs, active };
}
return { ...node, children: node.children.map(strip) };
};
return tree === null ? null : prune(strip(tree));
};
var setTabs = (tree, groupId, tabIds) => {
const group = findGroupById(tree, groupId);
if (tree === null || group === null) return tree;
const existing = new Set(group.tabs);
const seen = /* @__PURE__ */ new Set();
const reordered = tabIds.filter((id) => {
if (!existing.has(id) || seen.has(id)) return false;
seen.add(id);
return true;
});
const tabs = [...reordered, ...group.tabs.filter((id) => !seen.has(id))];
if (tabs.length === 0) return tree;
const activeId = group.tabs[group.active];
const active = Math.max(
tabs.findIndex((id) => id === activeId),
0
);
const visit = (node) => {
if (isGroup(node)) {
return node.id === groupId ? { ...node, tabs, active } : node;
}
return { ...node, children: node.children.map(visit) };
};
return visit(tree);
};
var activateTab = (tree, tabId) => {
if (tree === null) return null;
const visit = (node) => {
if (isGroup(node)) {
const index = node.tabs.indexOf(tabId);
return index === -1 || index === node.active ? node : { ...node, active: index };
}
return { ...node, children: node.children.map(visit) };
};
return visit(tree);
};
var moveTab = (tree, tabId, groupId, index) => {
if (tree === null) return null;
const target = findGroupById(tree, groupId);
if (target === null) return tree;
const source = findGroupOfTab(tree, tabId);
const withoutTab = source === null ? tree : closeTab(tree, tabId) ?? null;
if (withoutTab === null) return singleGroup([tabId], groupId);
if (findGroupById(withoutTab, groupId) === null) return tree;
const insert = (node) => {
if (isGroup(node)) {
if (node.id !== groupId) return node;
const at = Math.min(Math.max(index, 0), node.tabs.length);
const tabs = [...node.tabs.slice(0, at), tabId, ...node.tabs.slice(at)];
return { ...node, tabs, active: at };
}
return { ...node, children: node.children.map(insert) };
};
return prune(insert(withoutTab));
};
var stackInto = (tree, groupId, tabId) => {
const group = findGroupById(tree, groupId);
return group === null ? tree : moveTab(tree, tabId, groupId, group.tabs.length);
};
var zoneAxis = (zone) => zone === "left" || zone === "right" ? "row" : "col";
var zoneLeads = (zone) => zone === "left" || zone === "top";
var splitAt = (tree, groupId, zone, tabId) => {
if (tree === null) return singleGroup([tabId], "g0");
if (zone === "center") return stackInto(tree, groupId, tabId);
if (findGroupById(tree, groupId) === null) return tree;
const lifted = closeTab(tree, tabId);
if (lifted === null) return singleGroup([tabId], groupId);
const host = findGroupById(lifted, groupId);
if (host === null) return tree;
const newGroup = {
kind: "group",
id: nextGroupId(lifted),
tabs: [tabId],
active: 0
};
const dir = zoneAxis(zone);
const place = (node) => {
if (isGroup(node)) {
if (node.id !== groupId) return node;
return split(dir, zoneLeads(zone) ? [newGroup, node] : [node, newGroup]);
}
return { ...node, children: node.children.map(place) };
};
return prune(place(lifted));
};
var appendPane = (tree, tabId, dir = "row") => {
if (tree === null) return singleGroup([tabId]);
const lifted = closeTab(tree, tabId);
if (lifted === null) return singleGroup([tabId]);
const newGroup = {
kind: "group",
id: nextGroupId(lifted),
tabs: [tabId],
active: 0
};
const children = isSplit(lifted) && lifted.dir === dir ? [...lifted.children, newGroup] : [lifted, newGroup];
return prune(split(dir, children));
};
var nodeAtPath = (tree, path) => {
let node = tree;
for (const index of path) {
if (node === null || !isSplit(node)) return null;
node = node.children[index] ?? null;
}
return node;
};
var resize = (tree, path, gutterIndex, delta, minFraction = 0) => {
const target = nodeAtPath(tree, path);
if (tree === null || target === null || !isSplit(target)) return tree;
const before = target.sizes[gutterIndex];
const after = target.sizes[gutterIndex + 1];
if (before === void 0 || after === void 0) return tree;
const budget = before + after;
const min = Math.min(minFraction, budget / 2);
const nextBefore = Math.min(Math.max(before + delta, min), budget - min);
if (Math.abs(nextBefore - before) < EPSILON) return tree;
const sizes = [...target.sizes];
sizes[gutterIndex] = nextBefore;
sizes[gutterIndex + 1] = budget - nextBefore;
const replace = (node, depth) => {
if (depth === path.length) return { ...node, sizes };
const index = path[depth];
const children = [...node.children];
children[index] = replace(children[index], depth + 1);
return { ...node, children };
};
return replace(tree, 0);
};
var resizeFromPointer = (original, path, gutterIndex, deltaPx, extent, minFraction = 0) => {
if (extent <= 0) return original;
return resize(original, path, gutterIndex, deltaPx / extent, minFraction);
};
var layoutRects = (tree, box, gutter = 0) => {
const out = {};
const walk = (node, rect) => {
if (isGroup(node)) {
out[node.id] = rect;
return;
}
const horizontal = node.dir === "row";
const gutters = gutter * (node.children.length - 1);
const available = Math.max(
(horizontal ? rect.width : rect.height) - gutters,
0
);
let offset = horizontal ? rect.left : rect.top;
node.children.forEach((child, index) => {
const extent = available * (node.sizes[index] ?? 0);
walk(
child,
horizontal ? { left: offset, top: rect.top, width: extent, height: rect.height } : { left: rect.left, top: offset, width: rect.width, height: extent }
);
offset += extent + gutter;
});
};
if (tree !== null) {
walk(tree, { left: 0, top: 0, width: box.w, height: box.h });
}
return out;
};
var splitterHandles = (tree, box, gutter = 0) => {
const handles = [];
const walk = (node, rect, path) => {
if (isGroup(node)) return;
const horizontal = node.dir === "row";
const gutters = gutter * (node.children.length - 1);
const available = Math.max(
(horizontal ? rect.width : rect.height) - gutters,
0
);
let offset = horizontal ? rect.left : rect.top;
node.children.forEach((child, index) => {
const extent = available * (node.sizes[index] ?? 0);
const childRect = horizontal ? { left: offset, top: rect.top, width: extent, height: rect.height } : { left: rect.left, top: offset, width: rect.width, height: extent };
walk(child, childRect, [...path, index]);
offset += extent;
if (index < node.children.length - 1) {
handles.push({
path,
gutterIndex: index,
dir: node.dir,
extent: available,
rect: horizontal ? {
left: offset,
top: rect.top,
width: gutter,
height: rect.height
} : {
left: rect.left,
top: offset,
width: rect.width,
height: gutter
}
});
offset += gutter;
}
});
};
if (tree !== null) {
walk(tree, { left: 0, top: 0, width: box.w, height: box.h }, []);
}
return handles;
};
var canSplit = (tree, groupId, zone, min, box, gutter = 0) => {
if (zone === "center") return true;
const rect = layoutRects(tree, box, gutter)[groupId];
if (!rect) return false;
return zoneAxis(zone) === "row" ? (rect.width - gutter) / 2 >= min.w : (rect.height - gutter) / 2 >= min.h;
};
var zoneAt = (point, rect, edge = 0.25) => {
const x = rect.width > 0 ? (point.x - rect.left) / rect.width : 0.5;
const y = rect.height > 0 ? (point.y - rect.top) / rect.height : 0.5;
const distances = [
["left", x],
["right", 1 - x],
["top", y],
["bottom", 1 - y]
];
const [zone, distance] = distances.reduce(
(best, entry) => entry[1] < best[1] ? entry : best
);
return distance <= edge ? zone : "center";
};
var isRawGroup = (value) => value.kind === "group" && typeof value.id === "string" && Array.isArray(value.tabs) && value.tabs.every((tab) => typeof tab === "string");
var isRawSplit = (value) => value.kind === "split" && (value.dir === "row" || value.dir === "col") && Array.isArray(value.children);
var repairLayout = (raw, known) => {
const seen = /* @__PURE__ */ new Set();
const rebuild = (value, depth = 0) => {
if (depth > MAX_STORED_DEPTH) return null;
if (typeof value !== "object" || value === null) return null;
const record = value;
if (isRawGroup(record)) {
const tabs = record.tabs.filter((tab) => {
if (!known.has(tab) || seen.has(tab)) return false;
seen.add(tab);
return true;
});
if (tabs.length === 0) return null;
const active = typeof record.active === "number" && Number.isInteger(record.active) ? Math.min(Math.max(record.active, 0), tabs.length - 1) : 0;
return { kind: "group", id: String(record.id), tabs, active };
}
if (isRawSplit(record)) {
const children = record.children.map((child) => rebuild(child, depth + 1)).filter((child) => child !== null);
if (children.length === 0) return null;
const rawSizes = Array.isArray(record.sizes) ? record.sizes.filter(
(size) => typeof size === "number"
) : [];
return split(record.dir, children, rawSizes);
}
return null;
};
const rebuilt = prune(rebuild(raw));
if (rebuilt !== null) return dedupeIds(rebuilt);
const salvaged = collectKnownIds(raw, known);
return singleGroup(salvaged);
};
var collectKnownIds = (raw, known) => {
const found = [];
const seen = /* @__PURE__ */ new Set();
const visited = /* @__PURE__ */ new WeakSet();
const walk = (value) => {
if (typeof value === "string") {
if (known.has(value) && !seen.has(value)) {
seen.add(value);
found.push(value);
}
return;
}
if (typeof value !== "object" || value === null) return;
if (visited.has(value)) return;
visited.add(value);
if (Array.isArray(value)) {
value.forEach(walk);
return;
}
Object.values(value).forEach(walk);
};
walk(raw);
return found;
};
var dedupeIds = (tree) => {
const used = /* @__PURE__ */ new Set();
let counter = 0;
const visit = (node) => {
if (isGroup(node)) {
if (!used.has(node.id)) {
used.add(node.id);
return node;
}
let id = `g${counter++}`;
while (used.has(id)) id = `g${counter++}`;
used.add(id);
return { ...node, id };
}
return { ...node, children: node.children.map(visit) };
};
return visit(tree);
};
// 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/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) {
const known = new Set(pluginIds);
const before = JSON.stringify(existingState.layout ?? null);
const raw = existingState.layout ?? singleGroup(existingState.activePlugins ?? []);
existingState.layout = repairLayout(raw, known);
delete existingState.activePlugins;
if (JSON.stringify(existingState.layout ?? null) !== before) {
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 layout = existingState?.layout ?? null;
const shouldFillWithDefaultOpenPlugins = flattenTabs(layout).length === 0 && pluginsWithIds.length > 0;
if (shouldFillWithDefaultOpenPlugins) {
layout = singleGroup(getDefaultActivePlugins(pluginsWithIds));
}
const state = {
...initialState,
plugins: pluginsWithIds,
state: {
...initialState.state,
...existingState,
layout
},
settings: {
...initialState.settings,
...config,
...settings
}
};
return state;
};
var DevtoolsProvider = (props) => {
const [store, setStore] = createStore(getExistingStateFromStorage(props.config, props.plugins));
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(store.state));
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,
paneDragBridge: {
handler: null
},
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
}));
},
replaceLayout: (next) => {
setStore("state", "layout", next === null ? null : reconcile(next, {
key: null
}));
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(store.state));
}
};
return createComponent(DevtoolsContext.Provider, {
value,
get children() {
return props.children;
}
});
};
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.");
}
if (import.meta.hot && typeof import.meta.hot.on === "function") {
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);
}
});
ensureDevtoolsStyles(pip.document);
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$1(PiPContext.Provider, {
value,
get children() {
return props.children;
}
});
};
var createPiPWindow = () => {
const context = createMemo(() => {
const ctx = useContext(PiPContext);
if (!ctx) {
throw new Error("createPiPWindow must be used within a PiPProvider");
}
return ctx();
});
return context;
};
export { DevtoolsContext, DevtoolsProvider, MAX_ACTIVE_PLUGINS, MIN_PANE_SIZE, PANEL_CLOSE_THRESHOLD, PANEL_MAX_VIEWPORT_RATIO, PANE_CARD_INSET, PANE_DROP_EDGE_RATIO, PLUGINS_STRIP_HEIGHT, PLUGIN_GROUP_TAB_HEIGHT, PLUGIN_SPLITTER_SIZE, PiPProvider, TANSTACK_DEVTOOLS, WORKBENCH_GUTTER, WORKBENCH_GUTTER_NARROW, WORKBENCH_HEADER_HEIGHT, activateTab, allGroups, appendPane, canSplit, closeTab, createPiPWindow, findGroupOfTab, flattenTabs, getAllPermutations, layoutRects, moveTab, resize, resizeFromPointer, setTabs, singleGroup, splitAt, splitterHandles, stackInto, uppercaseFirstLetter, zoneAt };

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

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

import { DevtoolsProvider, PiPProvider } from '../chunk/USF7DF4E.js';
import '../chunk/GZI3DACQ.js';
import { render, createComponent, Portal } from 'solid-js/web';
import { lazy } from 'solid-js';
import { ClientEventBus } from '@tanstack/devtools-event-bus/client';
function mountDevtools(options) {
const {
el,
plugins,
config,
eventBusConfig,
onSetPlugins
} = options;
const eventBus = new ClientEventBus(eventBusConfig);
eventBus.start();
const Devtools = lazy(() => import('../devtools/3GWKGGQS.js'));
const dispose = render(() => createComponent(DevtoolsProvider, {
plugins,
config,
onSetPlugins,
get children() {
return createComponent(PiPProvider, {
get children() {
return createComponent(Portal, {
mount: el,
get children() {
return createComponent(Devtools, {});
}
});
}
});
}
}), el);
return {
dispose,
eventBus
};
}
export { mountDevtools };
import { DevtoolsProvider, PiPProvider } from '../chunk/USF7DF4E.js';
import '../chunk/GZI3DACQ.js';
import { render, createComponent, Portal } from 'solid-js/web';
import { lazy } from 'solid-js';
import { ClientEventBus } from '@tanstack/devtools-event-bus/client';
function mountDevtools(options) {
const {
el,
plugins,
config,
eventBusConfig,
onSetPlugins
} = options;
const eventBus = new ClientEventBus(eventBusConfig);
eventBus.start();
const Devtools = lazy(() => import('../devtools/JWB4EURD.js'));
const dispose = render(() => createComponent(DevtoolsProvider, {
plugins,
config,
onSetPlugins,
get children() {
return createComponent(PiPProvider, {
get children() {
return createComponent(Portal, {
mount: el,
get children() {
return createComponent(Devtools, {});
}
});
}
});
}
}), el);
return {
dispose,
eventBus
};
}
export { mountDevtools };
import { createUniqueId } from 'solid-js'
// The rainbow favicon paints its own colours so it stays readable on any host
// page. These literals are the official dark-favicon stops, not theme tokens.
const PALM_INK = '#171717' // semantic-color-exempt: trigger-rainbow-mark
const RAINBOW_STOP_0 = '#FF5F5F' // semantic-color-exempt: trigger-rainbow-mark
const RAINBOW_STOP_1 = '#FFA05C' // semantic-color-exempt: trigger-rainbow-mark
const RAINBOW_STOP_2 = '#FFF27C' // semantic-color-exempt: trigger-rainbow-mark
const RAINBOW_STOP_3 = '#74DCFF' // semantic-color-exempt: trigger-rainbow-mark
/**
* The default trigger mark: the rainbow palm from the TanStack dark favicon,
* drawn as a circle. Unlike `TanStackEmblem`, it does not follow `currentColor`.
*/
export const TanStackTriggerMark = () => {
const gradientId = `tsd-trigger-mark-${createUniqueId()}`
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 18 18"
fill="none"
aria-hidden="true"
width="18"
height="18"
>
<circle
cx="9"
cy="9"
r="9"
fill={`url(#${gradientId})`}
fill-opacity="0.99"
/>
<path
d="M11.223 13.665C10.5529 13.665 10.1862 13.8488 9.89289 13.9958C9.63954 14.1227 9.43953 14.2229 8.99949 14.2229C8.55946 14.2229 8.35944 14.1227 8.10609 13.9958C7.81274 13.8488 7.44604 13.665 6.77599 13.665C6.10594 13.665 5.73925 13.8488 5.44589 13.9958C5.19254 14.1227 4.99252 14.2229 4.55249 14.2229V15.1984C5.22254 15.1984 5.58924 15.0146 5.88259 14.8677C6.13594 14.7407 6.33596 14.6405 6.77599 14.6405C7.21602 14.6405 7.41604 14.7407 7.66939 14.8677C7.96275 15.0146 8.32944 15.1984 8.99949 15.1984C9.66954 15.1984 10.0362 15.0146 10.3296 14.8677C10.5829 14.7407 10.783 14.6405 11.223 14.6405C11.663 14.6405 11.863 14.7407 12.1164 14.8677C12.4097 15.0146 12.7764 15.1984 13.4465 15.1984V14.2229C13.0065 14.2229 12.8064 14.1227 12.5531 13.9958C12.2597 13.8488 11.893 13.665 11.223 13.665Z"
fill={PALM_INK}
/>
<path
d="M12.5534 12.1082C12.26 11.9612 11.8933 11.7775 11.2233 11.7775C10.5532 11.7775 10.1865 11.9612 9.89316 12.1082C9.81648 12.1449 9.74648 12.1817 9.67314 12.2117C9.61647 12.1616 9.58313 12.0982 9.5798 12.0313L9.42312 6.80995L11.5433 8.72747C12.05 9.18513 12.82 8.59718 12.5067 7.98919C12.3333 7.65513 12.1 7.34445 11.8066 7.08054C11.3533 6.66964 10.8132 6.4191 10.2398 6.30886H12.83C13.5168 6.30886 13.6934 5.33674 13.0434 5.11292C12.6567 4.98263 12.2433 4.90914 11.8133 4.90914C11.0266 4.90914 10.2965 5.153 9.68647 5.5639L11.5433 3.88357C12.05 3.4259 11.5433 2.59743 10.9099 2.84798C10.5599 2.98828 10.2298 3.18872 9.93649 3.45597C9.47646 3.87355 9.16643 4.39802 8.99975 4.96927C8.83308 4.39802 8.52305 3.87355 8.06302 3.45597C7.76966 3.19206 7.43964 2.98828 7.08961 2.84798C6.45623 2.59743 5.94952 3.4259 6.45623 3.88357L8.31304 5.5639C7.70632 5.153 6.97627 4.90914 6.18621 4.90914C5.75618 4.90914 5.34281 4.97929 4.95612 5.11292C4.30607 5.3334 4.48608 6.30886 5.16947 6.30886H7.75966C7.18962 6.4191 6.64624 6.67298 6.19288 7.08054C5.89952 7.34445 5.66617 7.65179 5.49282 7.98919C5.17947 8.59384 5.94952 9.18179 6.45623 8.72747L8.54639 6.84002L8.38971 12.0347C8.38971 12.0982 8.35304 12.1583 8.30637 12.2084C8.2397 12.1783 8.17636 12.1483 8.10635 12.1115C7.813 11.9645 7.4463 11.7808 6.77625 11.7808C6.1062 11.7808 5.73951 11.9645 5.44615 12.1115C5.1928 12.2385 4.99279 12.3387 4.55275 12.3387V13.3141C5.2228 13.3141 5.5895 13.1304 5.88285 12.9834C6.13621 12.8565 6.33622 12.7563 6.77625 12.7563C7.21629 12.7563 7.4163 12.8565 7.66965 12.9834C7.96301 13.1304 8.3297 13.3141 8.99975 13.3141C9.66981 13.3141 10.0365 13.1304 10.3299 12.9834C10.5832 12.8565 10.7832 12.7563 11.2233 12.7563C11.6633 12.7563 11.8633 12.8565 12.1167 12.9834C12.41 13.1304 12.7767 13.3141 13.4468 13.3141V12.3387C13.0067 12.3387 12.8067 12.2385 12.5534 12.1115V12.1082Z"
fill={PALM_INK}
/>
<defs>
<linearGradient
id={gradientId}
x1="8"
y1="0"
x2="8"
y2="18"
gradientUnits="userSpaceOnUse"
>
<stop stop-color={RAINBOW_STOP_0} />
<stop offset="0.344449" stop-color={RAINBOW_STOP_1} />
<stop offset="0.733354" stop-color={RAINBOW_STOP_2} />
<stop offset="1" stop-color={RAINBOW_STOP_3} />
</linearGradient>
</defs>
</svg>
)
}
+2
-2
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/A767CXXU.js';
import { initialState } from './chunk/CUZKAGZ3.js';
import { initialState } from './chunk/GZI3DACQ.js';

@@ -31,3 +31,3 @@ // src/core.ts

const { signal } = this.#mountAbortController = new AbortController();
import('./mount-impl/DFMRUH45.js').then(({ mountDevtools }) => {
import('./mount-impl/RVI7YCPX.js').then(({ mountDevtools }) => {
if (signal.aborted) {

@@ -34,0 +34,0 @@ return;

@@ -77,3 +77,3 @@ import * as solid_js from 'solid-js';

* - "floating": freely draggable, position persisted in local storage
* @default "fixed"
* @default "floating"
*/

@@ -147,2 +147,8 @@ triggerMode: TriggerMode;

persistOpen: boolean;
/**
* Whether the secondary strip (plugin and SEO tabs) is folded behind the
* header. Kept in state so a reload does not steal that height back.
* @default false
*/
subheaderCollapsed: boolean;
};

@@ -149,0 +155,0 @@ plugins?: Array<TanStackDevtoolsPlugin>;

export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/A767CXXU.js';
import { initialState } from './chunk/CUZKAGZ3.js';
import { initialState } from './chunk/GZI3DACQ.js';

@@ -31,3 +31,3 @@ // src/core.ts

const { signal } = this.#mountAbortController = new AbortController();
import('./mount-impl/DFMRUH45.js').then(({ mountDevtools }) => {
import('./mount-impl/RVI7YCPX.js').then(({ mountDevtools }) => {
if (signal.aborted) {

@@ -34,0 +34,0 @@ return;

export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './chunk/A767CXXU.js';
import { initialState } from './chunk/CUZKAGZ3.js';
import { initialState } from './chunk/GZI3DACQ.js';

@@ -31,3 +31,3 @@ // src/core.ts

const { signal } = this.#mountAbortController = new AbortController();
import('./mount-impl/PNC6IC34.js').then(({ mountDevtools }) => {
import('./mount-impl/UZMC65JZ.js').then(({ mountDevtools }) => {
if (signal.aborted) {

@@ -34,0 +34,0 @@ return;

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

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

"@tanstack/devtools-client": "0.0.8",
"@tanstack/devtools-event-bus": "0.4.2",
"@tanstack/devtools-ui": "0.7.0"
"@tanstack/devtools-event-bus": "0.4.3",
"@tanstack/devtools-ui": "0.7.1"
},

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

@@ -162,3 +162,3 @@ ---

- `tanstack_devtools_settings` -- persisted settings
- `tanstack_devtools_state` -- persisted UI state (active tab, panel height, active plugins, persistOpen)
- `tanstack_devtools_state` -- persisted UI state (active tab, panel height, layout, persistOpen, subheaderCollapsed)

@@ -172,3 +172,4 @@ All config properties are optional. Defaults shown below:

hideUntilHover: false, // hide trigger until mouse hover
position: 'bottom-right', // trigger position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'middle-left' | 'middle-right'
position: 'bottom-right', // used when triggerMode is 'fixed': 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'middle-left' | 'middle-right'
triggerMode: 'floating', // 'floating' (default, draggable) | 'fixed'
panelLocation: 'bottom', // panel position: 'top' | 'bottom'

@@ -175,0 +176,0 @@ openHotkey: ['Control', '~'],

@@ -24,2 +24,3 @@ import {

MIN_PANE_SIZE,
PANE_CARD_INSET,
PANE_DROP_EDGE_RATIO,

@@ -38,2 +39,3 @@ PLUGIN_GROUP_TAB_HEIGHT,

resize,
resizeFromPointer,
setTabs,

@@ -49,2 +51,23 @@ singleGroup,

type Box = { w: number; h: number }
const insetRect = (rect: Rect, inset: number): Rect => ({
left: rect.left + inset,
top: rect.top + inset,
width: Math.max(rect.width - inset * 2, 0),
height: Math.max(rect.height - inset * 2, 0),
})
/** Widen the splitter so it fills the chrome between two cards. */
const expandSplitterRect = (handle: SplitterHandle, extra: number): Rect =>
handle.dir === 'row'
? {
...handle.rect,
left: handle.rect.left - extra,
width: handle.rect.width + extra * 2,
}
: {
...handle.rect,
top: handle.rect.top - extra,
height: handle.rect.height + extra * 2,
}
/** What the pointer or keyboard is currently carrying. */

@@ -89,3 +112,3 @@ type Held = { tabId: string } | null

activeIndex: number
rect: Rect | undefined
rect: Rect | null
heldTabId: string | null

@@ -250,7 +273,26 @@ titleOf: (id: string) => string

const groupRects = createMemo(() =>
layoutRects(layout(), box(), PLUGIN_SPLITTER_SIZE),
)
const paddedBox = createMemo(() => ({
w: Math.max(box().w - PANE_CARD_INSET * 2, 0),
h: Math.max(box().h - PANE_CARD_INSET * 2, 0),
}))
const shift = (rect: Rect): Rect => ({
...rect,
left: rect.left + PANE_CARD_INSET,
top: rect.top + PANE_CARD_INSET,
})
const groupRects = createMemo(() => {
const raw = layoutRects(layout(), paddedBox(), PLUGIN_SPLITTER_SIZE)
return Object.fromEntries(
Object.entries(raw).map(([id, rect]) => [id, shift(rect)]),
)
})
const handles = createMemo(() =>
splitterHandles(layout(), box(), PLUGIN_SPLITTER_SIZE),
splitterHandles(layout(), paddedBox(), PLUGIN_SPLITTER_SIZE).map(
(handle) => ({
...handle,
rect: shift(handle.rect),
}),
),
)

@@ -273,11 +315,21 @@ const groups = createMemo(() => allGroups(layout()))

/** The rounded card a group sits in, inset from the workspace chrome. */
const cardRect = (groupId: string): Rect | null =>
groupRects()[groupId] ?? null
const tabBarRect = (groupId: string): Rect | null => {
const card = cardRect(groupId)
if (!card) return null
return { ...card, height: PLUGIN_GROUP_TAB_HEIGHT }
}
/** Panes sit under their group's tab bar, so the bar's height comes off the top. */
const paneRect = (groupId: string): Rect | null => {
const rect = groupRects()[groupId]
if (!rect) return null
const card = cardRect(groupId)
if (!card) return null
return {
left: rect.left,
top: rect.top + PLUGIN_GROUP_TAB_HEIGHT,
width: rect.width,
height: Math.max(rect.height - PLUGIN_GROUP_TAB_HEIGHT, 0),
left: card.left,
top: card.top + PLUGIN_GROUP_TAB_HEIGHT,
width: card.width,
height: Math.max(card.height - PLUGIN_GROUP_TAB_HEIGHT, 0),
}

@@ -356,3 +408,3 @@ }

}
const zone = zoneAt(point, rect, PANE_DROP_EDGE_RATIO)
const zone = zoneAt(point, paneRect(groupId) ?? rect, PANE_DROP_EDGE_RATIO)
// A pane too small to split takes the tab as a stacked tab instead, so the

@@ -367,3 +419,3 @@ // gesture always does something sensible rather than being refused.

MIN_PANE_SIZE,
box(),
paddedBox(),
PLUGIN_SPLITTER_SIZE,

@@ -516,3 +568,9 @@ )

if (event.button !== 0) return
event.preventDefault()
const target = event.currentTarget
if (target instanceof HTMLElement) {
target.setPointerCapture(event.pointerId)
}
const start = handle.dir === 'row' ? event.clientX : event.clientY
const original = layout()
const minFraction =

@@ -523,11 +581,14 @@ handle.extent > 0

: 0
const previousUserSelect = document.body.style.userSelect
document.body.style.userSelect = 'none'
const move = (moveEvent: PointerEvent) => {
moveEvent.preventDefault()
const now = handle.dir === 'row' ? moveEvent.clientX : moveEvent.clientY
if (handle.extent <= 0) return
setLayout(
resize(
layout(),
resizeFromPointer(
original,
handle.path,
handle.gutterIndex,
(now - start) / handle.extent,
now - start,
handle.extent,
minFraction,

@@ -537,3 +598,10 @@ ),

}
const up = () => {
const up = (upEvent: PointerEvent) => {
if (
target instanceof HTMLElement &&
target.hasPointerCapture(upEvent.pointerId)
) {
target.releasePointerCapture(upEvent.pointerId)
}
document.body.style.userSelect = previousUserSelect
document.removeEventListener('pointermove', move)

@@ -664,5 +732,8 @@ document.removeEventListener('pointerup', up)

if (target.groupId === null) {
return { left: 0, top: 0, width: box().w, height: box().h }
return insetRect(
{ left: 0, top: 0, width: box().w, height: box().h },
PANE_CARD_INSET,
)
}
const rect = groupRects()[target.groupId]
const rect = cardRect(target.groupId)
if (!rect) return null

@@ -706,3 +777,3 @@ if (target.willStack) return rect

activeIndex={group.active}
rect={groupRects()[group.id]}
rect={tabBarRect(group.id)}
heldTabId={held()?.tabId ?? null}

@@ -776,35 +847,42 @@ titleOf={titleOf}

<Index each={handles()}>
{(handle) => (
<div
role="separator"
tabIndex={0}
data-tsd-control
data-tsd-separator="plugin-pane"
data-testid="plugin-splitter"
aria-orientation={
handle().dir === 'row' ? 'vertical' : 'horizontal'
}
aria-label="Resize plugin panes"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(
((handle().dir === 'row'
? handle().rect.left
: handle().rect.top) /
Math.max(handle().extent, 1)) *
100,
)}
class={styles().pluginSplitter(handle().dir)}
style={{
left: `${handle().rect.left}px`,
top: `${handle().rect.top}px`,
width: `${handle().rect.width}px`,
height: `${handle().rect.height}px`,
}}
// Read through the accessor at gesture time, so a gutter that has
// been re-measured since render still moves the right sizes.
onPointerDown={(event) => startSplitterDrag(handle(), event)}
onKeyDown={(event) => resizeFromKeyboard(handle(), event)}
/>
)}
{(handle) => {
// Accessor, not a snapshot: Index keeps this node for a given
// gutter index, so a const taken at mount would stay at the first
// geometry (two equal panes) after a third pane opens or the
// workspace is re-measured.
const rect = () => expandSplitterRect(handle(), PANE_CARD_INSET)
return (
<div
role="separator"
tabIndex={0}
data-tsd-control
data-tsd-separator="plugin-pane"
data-testid="plugin-splitter"
aria-orientation={
handle().dir === 'row' ? 'vertical' : 'horizontal'
}
aria-label="Resize plugin panes"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(
((handle().dir === 'row'
? handle().rect.left
: handle().rect.top) /
Math.max(handle().extent, 1)) *
100,
)}
class={styles().pluginSplitter(handle().dir)}
style={{
left: `${rect().left}px`,
top: `${rect().top}px`,
width: `${rect().width}px`,
height: `${rect().height}px`,
}}
// Read through the accessor at gesture time, so a gutter that has
// been re-measured since render still moves the right sizes.
onPointerDown={(event) => startSplitterDrag(handle(), event)}
onKeyDown={(event) => resizeFromKeyboard(handle(), event)}
/>
)
}}
</Index>

@@ -811,0 +889,0 @@ </Show>

@@ -23,3 +23,6 @@ import { render } from '@solidjs/testing-library'

it('renders the trigger button with position/animation classes when not hidden', () => {
const { queryByLabelText } = renderTrigger({ position: 'bottom-right' })
const { queryByLabelText } = renderTrigger({
position: 'bottom-right',
triggerMode: 'fixed',
})

@@ -36,2 +39,12 @@ const button = queryByLabelText('Open TanStack Devtools')

it('paints the default trigger with the rainbow palm mark', () => {
const { queryByLabelText } = renderTrigger()
const svg = queryByLabelText('Open TanStack Devtools')?.querySelector('svg')
expect(svg).not.toBeNull()
expect(svg?.getAttribute('viewBox')).toBe('0 0 18 18')
expect(svg?.querySelector('circle')).not.toBeNull()
expect(svg?.querySelector('linearGradient')).not.toBeNull()
})
it('does not render the trigger button when triggerHidden is true', () => {

@@ -38,0 +51,0 @@ const { queryByLabelText } = renderTrigger({ triggerHidden: true })

@@ -12,3 +12,3 @@ import {

import { createStyles } from '../styles/use-styles'
import { TanStackEmblem } from './tanstack-emblem'
import { TanStackTriggerMark } from './tanstack-trigger-mark'
import type { TriggerCoords } from '../context/devtools-store'

@@ -167,2 +167,3 @@ import type { Accessor } from 'solid-js'

if (!dragging) return
e.preventDefault()
const el = buttonRef()

@@ -299,3 +300,6 @@ if (!el) return

>
<Show when={settings().customTrigger} fallback={<TanStackEmblem />}>
<Show
when={settings().customTrigger}
fallback={<TanStackTriggerMark />}
>
<div ref={setContainerRef} />

@@ -302,0 +306,0 @@ </Show>

@@ -55,3 +55,3 @@ import type { TabName } from '../tabs'

* - "floating": freely draggable, position persisted in local storage
* @default "fixed"
* @default "floating"
*/

@@ -127,2 +127,8 @@ triggerMode: TriggerMode

persistOpen: boolean
/**
* Whether the secondary strip (plugin and SEO tabs) is folded behind the
* header. Kept in state so a reload does not steal that height back.
* @default false
*/
subheaderCollapsed: boolean
}

@@ -137,3 +143,3 @@ plugins?: Array<TanStackDevtoolsPlugin>

position: 'bottom-right',
triggerMode: 'fixed',
triggerMode: 'floating',
triggerCoords: undefined,

@@ -160,3 +166,4 @@ panelLocation: 'bottom',

persistOpen: false,
subheaderCollapsed: false,
},
}

@@ -1,7 +0,2 @@

import {
createMemo,
createSignal,
useContext as getContext,
onCleanup,
} from 'solid-js'
import { createMemo, useContext as getContext, onCleanup } from 'solid-js'
import { MAX_ACTIVE_PLUGINS } from '../utils/constants.js'

@@ -120,17 +115,20 @@ import { appendPane, closeTab, flattenTabs } from '../utils/layout-tree.js'

/**
* Whether the strip and destination content are collapsed behind the main
* header. Deliberately not part of the persisted store: it is a momentary view
* state, and a reload should bring the panel back in full.
*
* The shell mounts once per document, so one module-level signal is enough and
* saves threading the toggle through every strip that hosts the control.
* Whether the secondary strip is folded behind the header. Lives on the
* persisted store so a reload keeps the height the user folded away.
*/
const [collapsed, setCollapsed] = createSignal(false)
export const createCollapsed = () => {
const { state, setState } = createDevtoolsState()
const isCollapsed = createMemo(() => state().subheaderCollapsed)
const setCollapsed = (value: boolean | ((previous: boolean) => boolean)) => {
const next =
typeof value === 'function' ? value(state().subheaderCollapsed) : value
setState({ subheaderCollapsed: next })
}
return {
isCollapsed,
toggleCollapsed: () => setCollapsed((previous) => !previous),
setCollapsed,
}
}
export const createCollapsed = () => ({
isCollapsed: collapsed,
toggleCollapsed: () => setCollapsed((previous) => !previous),
setCollapsed,
})
/**

@@ -137,0 +135,0 @@ * Handing a drag from the Plugins strip over to the workspace.

@@ -1,2 +0,2 @@

import { Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { Show, createEffect, createSignal, onCleanup } from 'solid-js'
import { createShortcut } from '@solid-primitives/keyboard'

@@ -77,6 +77,3 @@ import { Portal } from 'solid-js/web'

const [isResizing, setIsResizing] = createSignal(false)
const { isCollapsed, setCollapsed } = createCollapsed()
// The fold flag outlives a single mount, so start every shell with the
// subheader showing rather than inheriting a fold from a previous instance.
onMount(() => setCollapsed(false))
const { isCollapsed } = createCollapsed()
const [showMarketplace, setShowMarketplace] = createSignal(false)

@@ -124,16 +121,19 @@ const themeOwner = Symbol('tanstack-devtools-theme')

if (startEvent.button !== 0 || !panelElement) return
startEvent.preventDefault()
setIsResizing(true)
const dragInfo = {
originalHeight: panelElement.getBoundingClientRect().height,
pageY: startEvent.pageY,
}
const originalHeight = panelElement.getBoundingClientRect().height
const startY = startEvent.clientY
const previousUserSelect = document.body.style.userSelect
document.body.style.userSelect = 'none'
const run = (moveEvent: MouseEvent) => {
const delta = dragInfo.pageY - moveEvent.pageY
moveEvent.preventDefault()
const delta = startY - moveEvent.clientY
const newHeight =
settings().panelLocation === 'bottom'
? dragInfo.originalHeight + delta
: dragInfo.originalHeight - delta
? originalHeight + delta
: originalHeight - delta
updateHeight(newHeight)
}
const stop = () => {
document.body.style.userSelect = previousUserSelect
setIsResizing(false)

@@ -140,0 +140,0 @@ document.removeEventListener('mousemove', run)

@@ -86,2 +86,27 @@ import * as goober from 'goober'

const css = goober.css
// Core scrollers only. Do not put this on a selector that reaches into a
// plugin's own markup — plugins own the scrollbars inside their pane.
const thinScrollbars = `
scrollbar-width: thin;
scrollbar-color: ${semantic.color.border.control} transparent;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: ${semantic.color.border.control};
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
&::-webkit-scrollbar-thumb:hover {
background-color: ${semantic.color.text.muted};
}
&::-webkit-scrollbar-corner {
background: transparent;
}
`

@@ -104,2 +129,3 @@ return {

overscroll-behavior: contain;
${thinScrollbars}
`,

@@ -644,3 +670,3 @@ seoPreviewSection: css`

align-items: center;
gap: ${semantic.gap.tight};
gap: ${semantic.gap.control};
min-width: 0;

@@ -664,4 +690,5 @@ box-sizing: border-box;

white-space: nowrap;
scrollbar-width: thin;
transition: all 0.3s ease;
${thinScrollbars}
transition: opacity 0.3s ease, height 0.3s ease, padding 0.3s ease,
border-color 0.3s ease;
/* Tabs must not shift as the strip scrolls, but they still animate their

@@ -782,32 +809,29 @@ own hover and selected states. */

mainCloseBtnDefault: css`
background: ${semantic.color.surface.brand};
color: ${semantic.color.text.primary};
width: 60px;
height: 60px;
/* The rainbow mark paints its own circle. Keep the button transparent
so a theme fill does not frame or wash over it. 56px matches the
trigger size before the square chip. */
background: transparent;
width: 56px;
height: 56px;
justify-content: center;
border-radius: 14px;
/*
* Two inset layers carry the chip's finish so both can animate:
* a 1px edge ring, transparent at rest so it fades in on hover, and a
* full-bleed tint that lifts the brand fill off pitch black in dark mode
* (and off flat cream in light) without replacing it.
*/
border-radius: 50%;
box-shadow:
inset 0 0 0 1px transparent,
inset 0 0 0 999px ${semantic.color.state.hover},
${semantic.shadow.sm};
transition: all 0.3s ease;
/* Sized by height with width following the emblem's own tall aspect, so
the mark fills the chip instead of being letterboxed inside a square. */
/* Never transition left/top: floating mode writes those inline on every
pointer move, and animating them makes the mark lag then overshoot. */
transition:
opacity 0.3s ease,
box-shadow 0.3s ease,
scale 0.3s ease;
& > svg {
width: auto;
height: 48px;
display: block;
width: 100%;
height: 100%;
outline: none;
transition: all 0.3s ease;
}
/*
* Hover keeps the brand fill: this chip floats over the user's page, so
* replacing the fill with a translucent state colour would make it
* vanish. Hover deepens the resting tint one step, brings in the edge
* ring, and scales the chip up a touch.
* Hover keeps the rainbow fill: this chip floats over the user's page,
* so a translucent overlay would muddy the gradient. Hover brings in
* the edge ring and scales the chip up a touch.
*

@@ -821,9 +845,5 @@ * It animates the scale property rather than a transform: floating mode

inset 0 0 0 1px ${semantic.color.border.control},
inset 0 0 0 999px ${semantic.color.state.pressed},
${semantic.shadow.overlay};
scale: 1.06;
}
&:hover > svg {
scale: 1.04;
}
&:active {

@@ -834,7 +854,3 @@ scale: 0.98;

transition-property: opacity;
& > svg {
transition: none;
}
&:hover,
&:hover > svg,
&:active {

@@ -947,3 +963,7 @@ scale: 1;

overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
${thinScrollbars}
background: ${semantic.color.surface.workspace};
border-radius: 0 0 ${semantic.radius.overlay} ${semantic.radius.overlay};
`,

@@ -1006,3 +1026,3 @@ pluginsEmptyState: css`

overflow: hidden;
background: ${semantic.color.surface.workspace};
background: ${semantic.color.surface.brand};
`,

@@ -1029,6 +1049,6 @@ /**

align-items: stretch;
gap: 2px;
gap: ${semantic.gap.tight};
height: ${PLUGIN_GROUP_TAB_HEIGHT}px;
min-width: 0;
padding-inline: 4px;
padding: ${semantic.space[1]};
box-sizing: border-box;

@@ -1038,5 +1058,5 @@ overflow-x: auto;

white-space: nowrap;
scrollbar-width: thin;
background: ${semantic.color.surface.brand};
border-bottom: 1px solid ${semantic.color.state.pressed};
${thinScrollbars}
background: ${semantic.color.surface.workspace};
border-radius: ${semantic.radius.overlay} ${semantic.radius.overlay} 0 0;
`,

@@ -1055,5 +1075,8 @@ /**

background: transparent;
border: 0;
border-radius: ${semantic.radius.control};
box-sizing: border-box;
transition: background 0.2s ease;
&[data-tsd-selected='true'] {
background: ${semantic.color.surface.workspace};
background: ${semantic.color.surface.brand};
}

@@ -1135,18 +1158,33 @@ &:hover:not([data-tsd-selected='true']) {

position: absolute;
z-index: 2;
background-color: transparent;
transition: background-color 0.2s ease;
z-index: 20;
box-sizing: border-box;
background: transparent;
cursor: ${dir === 'row' ? 'col-resize' : 'row-resize'};
touch-action: none;
&:hover,
&:focus-visible {
background-color: ${semantic.color.border.focus};
user-select: none;
pointer-events: auto;
&::after {
content: '';
position: absolute;
pointer-events: none;
background: transparent;
border-radius: 999px;
transition: background-color 0.15s ease;
${dir === 'row'
? 'top: 8px; bottom: 8px; left: 50%; width: 4px; transform: translateX(-50%);'
: 'left: 8px; right: 8px; top: 50%; height: 4px; transform: translateY(-50%);'}
}
&:hover::after,
&:focus-visible::after {
background: ${semantic.color.border.focus};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
&::after {
transition: none;
}
}
@media (forced-colors: active) {
&:hover,
&:focus-visible {
background-color: Highlight;
&:hover::after,
&:focus-visible::after {
background: Highlight;
}

@@ -1316,2 +1354,3 @@ }

overscroll-behavior: contain;
${thinScrollbars}
padding: ${WORKBENCH_GUTTER}px;

@@ -1547,2 +1586,3 @@ @media (max-width: 430px) {

overscroll-behavior: contain;
${thinScrollbars}
`,

@@ -1549,0 +1589,0 @@ pluginMarketplaceGrid: css`

@@ -11,9 +11,15 @@ /**

/**
* Height of a group's tab bar, along the top edge of the group's rect. Tall
* enough for a 24px close target, which is the WCAG 2.5.8 minimum.
* Height of a group's tab bar, along the top edge of the group's rect. 4px of
* padding around the tabs (a small gutter from the card edge) plus a 24px
* close target, which is the WCAG 2.5.8 minimum.
*/
export const PLUGIN_GROUP_TAB_HEIGHT = 30
/** Thickness of the draggable gutter between two panes of a split. */
export const PLUGIN_SPLITTER_SIZE = 6
export const PLUGIN_GROUP_TAB_HEIGHT = 32
/** Space between a pane card and the workspace edge, and between cards. */
export const PANE_CARD_INSET = 8
/**
* Thickness of the draggable gutter between two panes of a split. Matches
* `PANE_CARD_INSET` so the middle gap equals the outer gap.
*/
export const PLUGIN_SPLITTER_SIZE = 8
/**
* A pane smaller than this is not worth having. A drop that would breach it in

@@ -20,0 +26,0 @@ * either axis becomes a stacked tab instead of a split, so the gesture always

@@ -17,2 +17,3 @@ import { describe, expect, it } from 'vitest'

resize,
resizeFromPointer,
singleGroup,

@@ -357,2 +358,26 @@ splitAt,

describe('resizeFromPointer', () => {
it('applies total mouse travel to the snapshot so later moves do not compound', () => {
const tree = split(
'row',
[group('g0', ['a']), group('g1', ['b'])],
[0.5, 0.5],
)
const extent = 1000
const at20 = resizeFromPointer(tree, [], 0, 20, extent) as SplitNode
const at40 = resizeFromPointer(tree, [], 0, 40, extent) as SplitNode
expect(at20.sizes[0]).toBeCloseTo(0.52, 10)
expect(at40.sizes[0]).toBeCloseTo(0.54, 10)
// Feeding the live tree a total delta (the old drag loop) overshoots:
// 0.5 + 0.02, then that result + 0.04 = 0.56.
const compounded = resizeFromPointer(at20, [], 0, 40, extent) as SplitNode
expect(compounded.sizes[0]).toBeCloseTo(0.56, 10)
})
it('is a no-op when the split has no measurable extent', () => {
const tree = split('row', [group('g0', ['a']), group('g1', ['b'])])
expect(resizeFromPointer(tree, [], 0, 20, 0)).toEqual(tree)
})
})
describe('layoutRects', () => {

@@ -359,0 +384,0 @@ it('fills the box with one group', () => {

@@ -431,2 +431,20 @@ /**

/**
* Apply a pointer drag to the layout as it was at pointer-down. `deltaPx` is
* the total movement from that start, not a per-frame increment. Always pass
* the snapshot: feeding the live tree a total delta compounds and the pane
* flies away from the cursor.
*/
export const resizeFromPointer = (
original: LayoutNode | null,
path: Path,
gutterIndex: number,
deltaPx: number,
extent: number,
minFraction = 0,
): LayoutNode | null => {
if (extent <= 0) return original
return resize(original, path, gutterIndex, deltaPx / extent, minFraction)
}
/**
* Rect per group, from a walk of the tree. Tabs in the same group share its

@@ -433,0 +451,0 @@ * rect because only the active one is displayed.

// src/context/devtools-store.ts
var keyboardModifiers = [
"Alt",
"Control",
"Meta",
"Shift",
"CtrlOrMeta"
];
var initialState = {
settings: {
defaultOpen: false,
hideUntilHover: false,
position: "bottom-right",
triggerMode: "fixed",
triggerCoords: void 0,
panelLocation: "bottom",
openHotkey: ["Control", "~"],
inspectHotkey: ["Shift", "Alt", "CtrlOrMeta"],
requireUrlFlag: false,
urlFlag: "tanstack-devtools",
theme: typeof window !== "undefined" && typeof window.matchMedia !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
sourceAction: "ide-warp",
triggerHidden: false,
customTrigger: void 0
},
state: {
activeTab: "plugins",
height: 400,
layout: null,
persistOpen: false
}
};
export { initialState, keyboardModifiers };
import { initialState } from './CUZKAGZ3.js';
import { createContext, createEffect, createComponent, createSignal, onCleanup, createMemo, useContext } from 'solid-js';
import { createStore, reconcile } from 'solid-js/store';
import { createComponent as createComponent$1, delegateEvents } from 'solid-js/web';
import { ensureDevtoolsStyles } from '@tanstack/devtools-ui/internal';
// src/utils/constants.ts
var MAX_ACTIVE_PLUGINS = 18;
var PLUGIN_GROUP_TAB_HEIGHT = 30;
var PLUGIN_SPLITTER_SIZE = 6;
var MIN_PANE_SIZE = { w: 280, h: 160 };
var PANE_DROP_EDGE_RATIO = 0.25;
var WORKBENCH_HEADER_HEIGHT = 36;
var PLUGINS_STRIP_HEIGHT = 44;
var WORKBENCH_GUTTER = 16;
var WORKBENCH_GUTTER_NARROW = 12;
var PANEL_CLOSE_THRESHOLD = 70;
var PANEL_MAX_VIEWPORT_RATIO = 0.9;
// src/utils/storage.ts
var getStorageItem = (key) => {
return 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/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/utils/layout-tree.ts
var EPSILON = 1e-9;
var MAX_STORED_DEPTH = 32;
var isGroup = (node) => node.kind === "group";
var isSplit = (node) => node.kind === "split";
var flattenTabs = (tree) => tree === null ? [] : isGroup(tree) ? [...tree.tabs] : tree.children.flatMap(flattenTabs);
var allGroups = (tree) => tree === null ? [] : isGroup(tree) ? [tree] : tree.children.flatMap(allGroups);
var findGroupOfTab = (tree, tabId) => allGroups(tree).find((group) => group.tabs.includes(tabId)) ?? null;
var findGroupById = (tree, groupId) => allGroups(tree).find((group) => group.id === groupId) ?? null;
var nextGroupId = (tree) => {
let highest = -1;
for (const group of allGroups(tree)) {
const match = /^g(\d+)$/.exec(group.id);
if (match) highest = Math.max(highest, Number(match[1]));
}
return `g${highest + 1}`;
};
var singleGroup = (tabs, id = "g0") => tabs.length === 0 ? null : { kind: "group", id, tabs: [...tabs], active: 0 };
var normalise = (sizes, count) => {
const usable = sizes.length === count && sizes.every((n) => Number.isFinite(n) && n > 0) ? sizes : Array.from({ length: count }, () => 1);
const total = usable.reduce((sum, n) => sum + n, 0);
return total > EPSILON ? usable.map((n) => n / total) : Array.from({ length: count }, () => 1 / count);
};
var split = (dir, children, sizes) => ({
kind: "split",
dir,
sizes: normalise(sizes ?? [], children.length),
children
});
var prune = (node) => {
if (node === null) return null;
if (isGroup(node)) {
if (node.tabs.length === 0) return null;
const active = Math.min(Math.max(node.active, 0), node.tabs.length - 1);
return active === node.active ? node : { ...node, active };
}
const kept = [];
const keptSizes = [];
node.children.forEach((child, index) => {
const pruned = prune(child);
if (pruned === null) return;
if (isSplit(pruned) && pruned.dir === node.dir) {
const share = node.sizes[index] ?? 1 / node.children.length;
pruned.children.forEach((grandchild, inner) => {
kept.push(grandchild);
keptSizes.push(share * (pruned.sizes[inner] ?? 0));
});
return;
}
kept.push(pruned);
keptSizes.push(node.sizes[index] ?? 1 / node.children.length);
});
if (kept.length === 0) return null;
if (kept.length === 1) return kept[0];
return split(node.dir, kept, keptSizes);
};
var closeTab = (tree, tabId) => {
const strip = (node) => {
if (isGroup(node)) {
const index = node.tabs.indexOf(tabId);
if (index === -1) return node;
const tabs = node.tabs.filter((id) => id !== tabId);
const active = node.active > index ? node.active - 1 : node.active;
return { ...node, tabs, active };
}
return { ...node, children: node.children.map(strip) };
};
return tree === null ? null : prune(strip(tree));
};
var setTabs = (tree, groupId, tabIds) => {
const group = findGroupById(tree, groupId);
if (tree === null || group === null) return tree;
const existing = new Set(group.tabs);
const seen = /* @__PURE__ */ new Set();
const reordered = tabIds.filter((id) => {
if (!existing.has(id) || seen.has(id)) return false;
seen.add(id);
return true;
});
const tabs = [...reordered, ...group.tabs.filter((id) => !seen.has(id))];
if (tabs.length === 0) return tree;
const activeId = group.tabs[group.active];
const active = Math.max(
tabs.findIndex((id) => id === activeId),
0
);
const visit = (node) => {
if (isGroup(node)) {
return node.id === groupId ? { ...node, tabs, active } : node;
}
return { ...node, children: node.children.map(visit) };
};
return visit(tree);
};
var activateTab = (tree, tabId) => {
if (tree === null) return null;
const visit = (node) => {
if (isGroup(node)) {
const index = node.tabs.indexOf(tabId);
return index === -1 || index === node.active ? node : { ...node, active: index };
}
return { ...node, children: node.children.map(visit) };
};
return visit(tree);
};
var moveTab = (tree, tabId, groupId, index) => {
if (tree === null) return null;
const target = findGroupById(tree, groupId);
if (target === null) return tree;
const source = findGroupOfTab(tree, tabId);
const withoutTab = source === null ? tree : closeTab(tree, tabId) ?? null;
if (withoutTab === null) return singleGroup([tabId], groupId);
if (findGroupById(withoutTab, groupId) === null) return tree;
const insert = (node) => {
if (isGroup(node)) {
if (node.id !== groupId) return node;
const at = Math.min(Math.max(index, 0), node.tabs.length);
const tabs = [...node.tabs.slice(0, at), tabId, ...node.tabs.slice(at)];
return { ...node, tabs, active: at };
}
return { ...node, children: node.children.map(insert) };
};
return prune(insert(withoutTab));
};
var stackInto = (tree, groupId, tabId) => {
const group = findGroupById(tree, groupId);
return group === null ? tree : moveTab(tree, tabId, groupId, group.tabs.length);
};
var zoneAxis = (zone) => zone === "left" || zone === "right" ? "row" : "col";
var zoneLeads = (zone) => zone === "left" || zone === "top";
var splitAt = (tree, groupId, zone, tabId) => {
if (tree === null) return singleGroup([tabId], "g0");
if (zone === "center") return stackInto(tree, groupId, tabId);
if (findGroupById(tree, groupId) === null) return tree;
const lifted = closeTab(tree, tabId);
if (lifted === null) return singleGroup([tabId], groupId);
const host = findGroupById(lifted, groupId);
if (host === null) return tree;
const newGroup = {
kind: "group",
id: nextGroupId(lifted),
tabs: [tabId],
active: 0
};
const dir = zoneAxis(zone);
const place = (node) => {
if (isGroup(node)) {
if (node.id !== groupId) return node;
return split(dir, zoneLeads(zone) ? [newGroup, node] : [node, newGroup]);
}
return { ...node, children: node.children.map(place) };
};
return prune(place(lifted));
};
var appendPane = (tree, tabId, dir = "row") => {
if (tree === null) return singleGroup([tabId]);
const lifted = closeTab(tree, tabId);
if (lifted === null) return singleGroup([tabId]);
const newGroup = {
kind: "group",
id: nextGroupId(lifted),
tabs: [tabId],
active: 0
};
const children = isSplit(lifted) && lifted.dir === dir ? [...lifted.children, newGroup] : [lifted, newGroup];
return prune(split(dir, children));
};
var nodeAtPath = (tree, path) => {
let node = tree;
for (const index of path) {
if (node === null || !isSplit(node)) return null;
node = node.children[index] ?? null;
}
return node;
};
var resize = (tree, path, gutterIndex, delta, minFraction = 0) => {
const target = nodeAtPath(tree, path);
if (tree === null || target === null || !isSplit(target)) return tree;
const before = target.sizes[gutterIndex];
const after = target.sizes[gutterIndex + 1];
if (before === void 0 || after === void 0) return tree;
const budget = before + after;
const min = Math.min(minFraction, budget / 2);
const nextBefore = Math.min(Math.max(before + delta, min), budget - min);
if (Math.abs(nextBefore - before) < EPSILON) return tree;
const sizes = [...target.sizes];
sizes[gutterIndex] = nextBefore;
sizes[gutterIndex + 1] = budget - nextBefore;
const replace = (node, depth) => {
if (depth === path.length) return { ...node, sizes };
const index = path[depth];
const children = [...node.children];
children[index] = replace(children[index], depth + 1);
return { ...node, children };
};
return replace(tree, 0);
};
var layoutRects = (tree, box, gutter = 0) => {
const out = {};
const walk = (node, rect) => {
if (isGroup(node)) {
out[node.id] = rect;
return;
}
const horizontal = node.dir === "row";
const gutters = gutter * (node.children.length - 1);
const available = Math.max(
(horizontal ? rect.width : rect.height) - gutters,
0
);
let offset = horizontal ? rect.left : rect.top;
node.children.forEach((child, index) => {
const extent = available * (node.sizes[index] ?? 0);
walk(
child,
horizontal ? { left: offset, top: rect.top, width: extent, height: rect.height } : { left: rect.left, top: offset, width: rect.width, height: extent }
);
offset += extent + gutter;
});
};
if (tree !== null) {
walk(tree, { left: 0, top: 0, width: box.w, height: box.h });
}
return out;
};
var splitterHandles = (tree, box, gutter = 0) => {
const handles = [];
const walk = (node, rect, path) => {
if (isGroup(node)) return;
const horizontal = node.dir === "row";
const gutters = gutter * (node.children.length - 1);
const available = Math.max(
(horizontal ? rect.width : rect.height) - gutters,
0
);
let offset = horizontal ? rect.left : rect.top;
node.children.forEach((child, index) => {
const extent = available * (node.sizes[index] ?? 0);
const childRect = horizontal ? { left: offset, top: rect.top, width: extent, height: rect.height } : { left: rect.left, top: offset, width: rect.width, height: extent };
walk(child, childRect, [...path, index]);
offset += extent;
if (index < node.children.length - 1) {
handles.push({
path,
gutterIndex: index,
dir: node.dir,
extent: available,
rect: horizontal ? {
left: offset,
top: rect.top,
width: gutter,
height: rect.height
} : {
left: rect.left,
top: offset,
width: rect.width,
height: gutter
}
});
offset += gutter;
}
});
};
if (tree !== null) {
walk(tree, { left: 0, top: 0, width: box.w, height: box.h }, []);
}
return handles;
};
var canSplit = (tree, groupId, zone, min, box, gutter = 0) => {
if (zone === "center") return true;
const rect = layoutRects(tree, box, gutter)[groupId];
if (!rect) return false;
return zoneAxis(zone) === "row" ? (rect.width - gutter) / 2 >= min.w : (rect.height - gutter) / 2 >= min.h;
};
var zoneAt = (point, rect, edge = 0.25) => {
const x = rect.width > 0 ? (point.x - rect.left) / rect.width : 0.5;
const y = rect.height > 0 ? (point.y - rect.top) / rect.height : 0.5;
const distances = [
["left", x],
["right", 1 - x],
["top", y],
["bottom", 1 - y]
];
const [zone, distance] = distances.reduce(
(best, entry) => entry[1] < best[1] ? entry : best
);
return distance <= edge ? zone : "center";
};
var isRawGroup = (value) => value.kind === "group" && typeof value.id === "string" && Array.isArray(value.tabs) && value.tabs.every((tab) => typeof tab === "string");
var isRawSplit = (value) => value.kind === "split" && (value.dir === "row" || value.dir === "col") && Array.isArray(value.children);
var repairLayout = (raw, known) => {
const seen = /* @__PURE__ */ new Set();
const rebuild = (value, depth = 0) => {
if (depth > MAX_STORED_DEPTH) return null;
if (typeof value !== "object" || value === null) return null;
const record = value;
if (isRawGroup(record)) {
const tabs = record.tabs.filter((tab) => {
if (!known.has(tab) || seen.has(tab)) return false;
seen.add(tab);
return true;
});
if (tabs.length === 0) return null;
const active = typeof record.active === "number" && Number.isInteger(record.active) ? Math.min(Math.max(record.active, 0), tabs.length - 1) : 0;
return { kind: "group", id: String(record.id), tabs, active };
}
if (isRawSplit(record)) {
const children = record.children.map((child) => rebuild(child, depth + 1)).filter((child) => child !== null);
if (children.length === 0) return null;
const rawSizes = Array.isArray(record.sizes) ? record.sizes.filter(
(size) => typeof size === "number"
) : [];
return split(record.dir, children, rawSizes);
}
return null;
};
const rebuilt = prune(rebuild(raw));
if (rebuilt !== null) return dedupeIds(rebuilt);
const salvaged = collectKnownIds(raw, known);
return singleGroup(salvaged);
};
var collectKnownIds = (raw, known) => {
const found = [];
const seen = /* @__PURE__ */ new Set();
const visited = /* @__PURE__ */ new WeakSet();
const walk = (value) => {
if (typeof value === "string") {
if (known.has(value) && !seen.has(value)) {
seen.add(value);
found.push(value);
}
return;
}
if (typeof value !== "object" || value === null) return;
if (visited.has(value)) return;
visited.add(value);
if (Array.isArray(value)) {
value.forEach(walk);
return;
}
Object.values(value).forEach(walk);
};
walk(raw);
return found;
};
var dedupeIds = (tree) => {
const used = /* @__PURE__ */ new Set();
let counter = 0;
const visit = (node) => {
if (isGroup(node)) {
if (!used.has(node.id)) {
used.add(node.id);
return node;
}
let id = `g${counter++}`;
while (used.has(id)) id = `g${counter++}`;
used.add(id);
return { ...node, id };
}
return { ...node, children: node.children.map(visit) };
};
return visit(tree);
};
// 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/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) {
const known = new Set(pluginIds);
const before = JSON.stringify(existingState.layout ?? null);
const raw = existingState.layout ?? singleGroup(existingState.activePlugins ?? []);
existingState.layout = repairLayout(raw, known);
delete existingState.activePlugins;
if (JSON.stringify(existingState.layout ?? null) !== before) {
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 layout = existingState?.layout ?? null;
const shouldFillWithDefaultOpenPlugins = flattenTabs(layout).length === 0 && pluginsWithIds.length > 0;
if (shouldFillWithDefaultOpenPlugins) {
layout = singleGroup(getDefaultActivePlugins(pluginsWithIds));
}
const state = {
...initialState,
plugins: pluginsWithIds,
state: {
...initialState.state,
...existingState,
layout
},
settings: {
...initialState.settings,
...config,
...settings
}
};
return state;
};
var DevtoolsProvider = (props) => {
const [store, setStore] = createStore(getExistingStateFromStorage(props.config, props.plugins));
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(store.state));
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,
paneDragBridge: {
handler: null
},
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
}));
},
replaceLayout: (next) => {
setStore("state", "layout", next === null ? null : reconcile(next, {
key: null
}));
setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(store.state));
}
};
return createComponent(DevtoolsContext.Provider, {
value,
get children() {
return props.children;
}
});
};
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.");
}
if (import.meta.hot && typeof import.meta.hot.on === "function") {
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);
}
});
ensureDevtoolsStyles(pip.document);
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$1(PiPContext.Provider, {
value,
get children() {
return props.children;
}
});
};
var createPiPWindow = () => {
const context = createMemo(() => {
const ctx = useContext(PiPContext);
if (!ctx) {
throw new Error("createPiPWindow must be used within a PiPProvider");
}
return ctx();
});
return context;
};
export { DevtoolsContext, DevtoolsProvider, MAX_ACTIVE_PLUGINS, MIN_PANE_SIZE, PANEL_CLOSE_THRESHOLD, PANEL_MAX_VIEWPORT_RATIO, PANE_DROP_EDGE_RATIO, PLUGINS_STRIP_HEIGHT, PLUGIN_GROUP_TAB_HEIGHT, PLUGIN_SPLITTER_SIZE, PiPProvider, TANSTACK_DEVTOOLS, WORKBENCH_GUTTER, WORKBENCH_GUTTER_NARROW, WORKBENCH_HEADER_HEIGHT, activateTab, allGroups, appendPane, canSplit, closeTab, createPiPWindow, findGroupOfTab, flattenTabs, getAllPermutations, layoutRects, moveTab, resize, setTabs, singleGroup, splitAt, splitterHandles, stackInto, uppercaseFirstLetter, zoneAt };

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

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

import { DevtoolsProvider, PiPProvider } from '../chunk/TI24F65M.js';
import '../chunk/CUZKAGZ3.js';
import { render, createComponent, Portal } from 'solid-js/web';
import { lazy } from 'solid-js';
import { ClientEventBus } from '@tanstack/devtools-event-bus/client';
function mountDevtools(options) {
const {
el,
plugins,
config,
eventBusConfig,
onSetPlugins
} = options;
const eventBus = new ClientEventBus(eventBusConfig);
eventBus.start();
const Devtools = lazy(() => import('../devtools/HH73YH4K.js'));
const dispose = render(() => createComponent(DevtoolsProvider, {
plugins,
config,
onSetPlugins,
get children() {
return createComponent(PiPProvider, {
get children() {
return createComponent(Portal, {
mount: el,
get children() {
return createComponent(Devtools, {});
}
});
}
});
}
}), el);
return {
dispose,
eventBus
};
}
export { mountDevtools };
import { DevtoolsProvider, PiPProvider } from '../chunk/TI24F65M.js';
import '../chunk/CUZKAGZ3.js';
import { render, createComponent, Portal } from 'solid-js/web';
import { lazy } from 'solid-js';
import { ClientEventBus } from '@tanstack/devtools-event-bus/client';
function mountDevtools(options) {
const {
el,
plugins,
config,
eventBusConfig,
onSetPlugins
} = options;
const eventBus = new ClientEventBus(eventBusConfig);
eventBus.start();
const Devtools = lazy(() => import('../devtools/3643KON3.js'));
const dispose = render(() => createComponent(DevtoolsProvider, {
plugins,
config,
onSetPlugins,
get children() {
return createComponent(PiPProvider, {
get children() {
return createComponent(Portal, {
mount: el,
get children() {
return createComponent(Devtools, {});
}
});
}
});
}
}), el);
return {
dispose,
eventBus
};
}
export { mountDevtools };