@tanstack/devtools
Version:
TanStack Devtools is a set of tools for building advanced devtools for your application.
6,262 lines • 224 kB
JavaScript
import { PLUGIN_TITLE_CONTAINER_ID, PLUGIN_CONTAINER_ID } from '../chunk/A767CXXU.js';
import { createPiPWindow, TANSTACK_DEVTOOLS, PANEL_CLOSE_THRESHOLD, getAllPermutations, PANEL_MAX_VIEWPORT_RATIO, DevtoolsContext, flattenTabs, WORKBENCH_GUTTER, WORKBENCH_GUTTER_NARROW, PLUGIN_GROUP_TAB_HEIGHT, PLUGINS_STRIP_HEIGHT, WORKBENCH_HEADER_HEIGHT, PANE_CARD_INSET, layoutRects, PLUGIN_SPLITTER_SIZE, splitterHandles, allGroups, MAX_ACTIVE_PLUGINS, closeTab, activateTab, moveTab, setTabs, appendPane, MIN_PANE_SIZE, resize, singleGroup, stackInto, splitAt, findGroupOfTab, zoneAt, PANE_DROP_EDGE_RATIO, canSplit, resizeFromPointer, uppercaseFirstLetter } from '../chunk/USF7DF4E.js';
import { keyboardModifiers } from '../chunk/GZI3DACQ.js';
import { delegateEvents, createComponent, Portal, use, setAttribute, insert, memo, effect, className, style, setStyleProperty, addEventListener, template, classList, spread, mergeProps } from 'solid-js/web';
import { createSignal, createEffect, onCleanup, Show, createMemo, untrack, For, useContext, createUniqueId, onMount, Index } from 'solid-js';
import { createShortcut, useKeyDownList } from '@solid-primitives/keyboard';
import { ThemeContextProvider, ChevronDownIcon, MainPanel as MainPanel$1, Section, SectionTitle, SectionIcon, SectionDescription, Checkbox, Select, Input, CloseIcon, SearchIcon, SettingsIcon, Button, PackageIcon as PackageIcon$1, ExternalLinkIcon, CheckCircleIcon, XCircleIcon } from '@tanstack/devtools-ui';
import { devtoolsEventClient } from '@tanstack/devtools-client';
import clsx from 'clsx';
import * as goober from 'goober';
import { resolveSemanticTheme } from '@tanstack/devtools-ui/internal';
import { Cogs, PiP, X, PackageIcon, SettingsCog, Link, Keyboard, GeoTag } from '@tanstack/devtools-ui/icons';
import { createSortable } from '@neodrag/solid/sortable';
import { createStore } from 'solid-js/store';
import { createElementSize } from '@solid-primitives/resize-observer';
import { createEventListener } from '@solid-primitives/event-listener';
var createDevtoolsContext = () => {
const context = useContext(DevtoolsContext);
if (context === void 0) {
throw new Error(
"createDevtoolsContext must be used within a ShellContextProvider"
);
}
return context;
};
function createTheme() {
const { settings, setSettings } = createDevtoolsSettings();
const theme = createMemo(() => settings().theme);
return {
theme,
setTheme: (theme2) => setSettings({ theme: theme2 })
};
}
var createPlugins = () => {
const { store, setStore } = createDevtoolsContext();
const plugins = createMemo(() => store.plugins);
const activePlugins = createMemo(() => flattenTabs(store.state.layout), [], {
equals: (a, b) => a.length === b.length && a.every((id, i) => id === b[i])
});
const layout = createMemo(() => store.state.layout);
const setLayout = (next) => {
setStore((previous) => ({
...previous,
state: { ...previous.state, layout: next }
}));
};
const toggleActivePlugins = (pluginId) => {
const current = store.state.layout;
const isActive = flattenTabs(current).includes(pluginId);
if (isActive) {
setLayout(closeTab(current, pluginId));
return;
}
if (flattenTabs(current).length >= MAX_ACTIVE_PLUGINS) return;
setLayout(appendPane(current, pluginId));
};
return { plugins, toggleActivePlugins, activePlugins, layout, setLayout };
};
var createDevtoolsState = () => {
const { store, setStore } = createDevtoolsContext();
const state = createMemo(() => store.state);
const setState = (newState) => {
setStore((previous) => ({
...previous,
state: {
...previous.state,
...newState
}
}));
};
return { state, setState };
};
var createDevtoolsSettings = () => {
const { store, setStore } = createDevtoolsContext();
const settings = createMemo(() => store.settings);
const setSettings = (newSettings) => {
setStore((previous) => ({
...previous,
settings: {
...previous.settings,
...newSettings
}
}));
};
return { setSettings, settings };
};
var createPersistOpen = () => {
const { state, setState } = createDevtoolsState();
const persistOpen = createMemo(() => state().persistOpen);
const setPersistOpen = (value) => {
setState({ persistOpen: value });
};
return { persistOpen, setPersistOpen };
};
var createCollapsed = () => {
const { state, setState } = createDevtoolsState();
const isCollapsed = createMemo(() => state().subheaderCollapsed);
const setCollapsed = (value) => {
const next = typeof value === "function" ? value(state().subheaderCollapsed) : value;
setState({ subheaderCollapsed: next });
};
return {
isCollapsed,
toggleCollapsed: () => setCollapsed((previous) => !previous),
setCollapsed
};
};
var createStripDrag = () => {
const { paneDragBridge } = createDevtoolsContext();
return {
/** Called by the workspace during setup; cleared when it goes away. */
acceptStripDrags: (handler) => {
paneDragBridge.handler = handler;
onCleanup(() => {
if (paneDragBridge.handler === handler) paneDragBridge.handler = null;
});
},
/** Called by the strip once a press has been held long enough to be a drag. */
beginStripDrag: (pluginId, point) => paneDragBridge.handler?.(pluginId, point)
};
};
var createHeight = () => {
const { state, setState } = createDevtoolsState();
const height = createMemo(() => state().height);
const setHeight = (newHeight) => {
setState({ height: newHeight });
};
return { height, setHeight };
};
var recursivelyChangeTabIndex = (node, remove = true) => {
if (remove) {
node.setAttribute("tabIndex", "-1");
} else {
node.removeAttribute("tabIndex");
}
for (const child of node.children) {
recursivelyChangeTabIndex(child, remove);
}
};
var createDisableTabbing = (isOpen) => {
createEffect(() => {
const el = document.getElementById(TANSTACK_DEVTOOLS);
if (!el) return;
recursivelyChangeTabIndex(el, !isOpen());
});
};
// src/utils/hotkey.ts
var normalizeHotkey = (keys) => {
if (!keys.includes("CtrlOrMeta")) {
return [keys];
}
return [
keys.map((key) => key === "CtrlOrMeta" ? "Control" : key),
keys.map((key) => key === "CtrlOrMeta" ? "Meta" : key)
];
};
var getHotkeyPermutations = (hotkey) => {
const normalizedHotkeys = normalizeHotkey(hotkey);
return normalizedHotkeys.flatMap((normalizedHotkey) => {
const modifiers = normalizedHotkey.filter(
(key) => keyboardModifiers.includes(key)
);
const nonModifiers = normalizedHotkey.filter(
(key) => !keyboardModifiers.includes(key)
);
if (modifiers.length === 0) {
return [nonModifiers];
}
const allModifierCombinations = getAllPermutations(modifiers);
return allModifierCombinations.map((combo) => [...combo, ...nonModifiers]);
});
};
var isHotkeyCombinationPressed = (keys, hotkey) => {
const permutations = getHotkeyPermutations(hotkey);
const pressedKeys = keys.map((key) => key.toUpperCase());
return permutations.some(
(combo) => (
// every key in the combo must be pressed
combo.every((key) => pressedKeys.includes(String(key).toUpperCase())) && // and no extra keys beyond the combo
pressedKeys.every(
(key) => combo.map((k) => String(k).toUpperCase()).includes(key)
)
)
);
};
var WORKBENCH_GEOMETRY_STYLE_ID = "tanstack-devtools-workbench-geometry";
var ensureWorkbenchGeometryStyles = (targetDocument) => {
if (targetDocument.getElementById(WORKBENCH_GEOMETRY_STYLE_ID)) return;
const style = targetDocument.createElement("style");
style.id = WORKBENCH_GEOMETRY_STYLE_ID;
style.textContent = `
@media (max-width: 360px) {
.tsd-workbench-wordmark { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.tsd-workbench-secondary-tabs, .tsd-workbench-secondary-tabs > * { transition: none !important; }
.tsd-motion-safe { animation: none !important; transition: none !important; transform: none !important; }
/* Core controls and surfaces animate on hover/active by default; drop all of
it in one place. Both markers are stamped by core only, so this never
reaches inside a plugin's own markup. */
[data-tsd-control], [data-tsd-surface] { transition: none !important; }
}`;
targetDocument.head.appendChild(style);
};
var fadeIn = goober.keyframes`
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
var slideInRight = goober.keyframes`
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
`;
var slideUp = goober.keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
var statusFadeIn = goober.keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
var spin = goober.keyframes`
to {
transform: rotate(360deg);
}
`;
var stylesFactory = (theme) => {
const semantic = resolveSemanticTheme(theme);
const css2 = goober.css;
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;
}
`;
return {
seoWorkspace: css2`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
`,
seoContent: css2`
flex: 1 1 auto;
height: auto;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
${thinScrollbars}
`,
seoPreviewSection: css2`
display: flex;
flex-direction: row;
gap: 16px;
margin-bottom: 0;
justify-content: flex-start;
align-items: flex-start;
overflow-x: auto;
flex-wrap: wrap;
padding-bottom: 0.5rem;
`,
seoPreviewCard: css2`
border: 1px solid ${semantic.color.border.decorative};
border-radius: ${semantic.radius.overlay};
padding: 12px;
background: ${semantic.color.surface.elevated};
margin-bottom: 0;
box-shadow: ${semantic.shadow.xs};
display: flex;
flex-direction: column;
align-items: stretch;
min-width: 200px;
max-width: 240px;
font-size: ${semantic.type.bodySm.size};
gap: ${semantic.gap.tight};
`,
seoPreviewHeader: css2`
display: flex;
align-items: center;
gap: 6px;
font-size: ${semantic.type.bodyXs.size};
font-weight: 600;
letter-spacing: ${semantic.type.labelSm.tracking};
text-transform: uppercase;
margin-bottom: 0;
color: ${semantic.color.text.secondary};
`,
/**
* The network's own brand colour survives as a small dot so the card can
* still be identified at a glance, without giving every card a different
* coloured outline.
*/
seoPreviewNetworkDot: css2`
width: 8px;
height: 8px;
flex: 0 0 8px;
border-radius: 50%;
box-shadow: inset 0 0 0 1px ${semantic.color.state.hover};
@media (forced-colors: active) {
forced-color-adjust: none;
}
`,
seoPreviewImage: css2`
width: 100%;
max-width: 100%;
box-sizing: border-box;
border-radius: ${semantic.radius.group};
margin-bottom: 6px;
background: ${semantic.color.surface.subtle};
height: 120px;
object-fit: cover;
`,
seoPreviewImagePlaceholder: css2`
display: flex;
align-items: center;
justify-content: center;
color: ${semantic.color.text.muted};
font-size: ${semantic.type.bodyXs.size};
border: 1px dashed ${semantic.color.border.decorative};
`,
seoPreviewTitle: css2`
font-family: ${semantic.font.display};
font-size: ${semantic.type.bodySm.size};
line-height: ${semantic.type.bodySm.lineHeight};
font-weight: 700;
margin-bottom: 2px;
color: ${semantic.color.text.primary};
`,
seoPreviewDesc: css2`
color: ${semantic.color.text.secondary};
margin-bottom: 4px;
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodyXs.lineHeight};
`,
seoPreviewUrl: css2`
color: ${semantic.color.text.muted};
font-family: ${semantic.font.mono};
font-size: 11px;
margin-bottom: 0;
word-break: break-all;
`,
seoMissingTagsSection: css2`
margin-top: 6px;
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodyXs.lineHeight};
color: ${semantic.color.status.error.text};
`,
seoMissingTagsList: css2`
margin: 4px 0 0 0;
padding: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 4px;
max-width: 240px;
`,
seoMissingTag: css2`
background: ${semantic.color.status.error.subtleFill};
color: ${semantic.color.status.error.text};
border-radius: ${semantic.radius.control};
padding: 2px 6px;
font-family: ${semantic.font.mono};
font-size: 11px;
font-weight: 500;
`,
/* No box of its own: the section already provides one, and the snippet
below provides another. A label plus spacing is enough. */
serpPreviewBlock: css2`
margin-bottom: ${WORKBENCH_GUTTER}px;
&:last-child {
margin-bottom: 0;
}
`,
serpPreviewLabel: css2`
font-size: ${semantic.type.bodyXs.size};
font-weight: 600;
letter-spacing: ${semantic.type.labelSm.tracking};
text-transform: uppercase;
margin-bottom: 6px;
color: ${semantic.color.text.secondary};
`,
serpSnippet: css2`
border: 1px solid ${semantic.color.border.decorative};
border-radius: 8px;
padding: 1rem 1.25rem;
background: ${semantic.color.surface.elevated};
max-width: 600px;
font-family: ${semantic.font.body};
box-shadow: ${semantic.shadow.xs};
`,
serpSnippetMobile: css2`
border: 1px solid ${semantic.color.border.decorative};
border-radius: 8px;
padding: 1rem 1.25rem;
background: ${semantic.color.surface.elevated};
max-width: 380px;
font-family: ${semantic.font.body};
box-shadow: ${semantic.shadow.xs};
`,
serpSnippetDescMobile: css2`
font-size: 0.875rem;
color: ${semantic.color.text.secondary};
margin: 0;
line-height: 1.5;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
`,
serpSnippetTopRow: css2`
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
`,
serpSnippetFavicon: css2`
width: 28px;
height: 28px;
border-radius: 50%;
flex-shrink: 0;
object-fit: contain;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
`,
serpSnippetDefaultFavicon: css2`
width: 28px;
height: 28px;
background-color: ${semantic.color.surface.subtle};
border-radius: 50%;
flex-shrink: 0;
object-fit: contain;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
`,
serpSnippetSiteColumn: css2`
display: flex;
flex-direction: column;
gap: 0;
min-width: 0;
`,
serpSnippetSiteName: css2`
font-size: 0.875rem;
color: ${semantic.color.text.primary};
line-height: 1.4;
margin: 0;
`,
serpSnippetSiteUrl: css2`
font-size: 0.75rem;
color: ${semantic.color.text.muted};
line-height: 1.4;
margin: 0;
`,
serpSnippetTitle: css2`
font-size: 1.25rem;
font-weight: 400;
color: ${semantic.color.text.link};
margin: 0 0 4px 0;
line-height: 1.3;
`,
serpSnippetDesc: css2`
font-size: 0.875rem;
color: ${semantic.color.text.secondary};
margin: 0;
line-height: 1.5;
`,
serpErrorList: css2`
margin: 4px 0 0 0;
padding-left: 1.25rem;
list-style-type: disc;
`,
serpReportItem: css2`
margin-top: 0.25rem;
color: ${semantic.color.status.error.text};
font-size: 0.875rem;
`,
devtoolsPanelContainer: (panelLocation, isDetached) => css2`
direction: ltr;
position: fixed;
overflow: visible;
${panelLocation}: 0;
inset-inline: 0;
z-index: 99999;
inline-size: 100%;
max-inline-size: 100%;
box-sizing: border-box;
${isDetached ? "" : "max-height: 90%;"}
border: 0;
box-shadow: none;
transition: transform 160ms ease-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
`,
devtoolsPanelContainerVisibility: (isOpen) => {
return css2`
visibility: ${isOpen ? "visible" : "hidden"};
height: ${isOpen ? "auto" : "0"};
`;
},
devtoolsPanelContainerResizing: (isResizing) => {
if (isResizing()) {
return css2`
transition: none;
`;
}
return css2`
transition: transform 160ms ease-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
`;
},
devtoolsDrawerContent: css2`
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
`,
devtoolsPanel: css2`
display: grid;
font-size: ${semantic.type.bodySm.size};
font-family: ${semantic.font.body};
background-color: ${semantic.color.surface.workspace};
color: ${semantic.color.text.primary};
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
grid-template-rows: ${WORKBENCH_HEADER_HEIGHT}px minmax(0, 1fr);
/* The strip row is auto-sized so the strip's own animated height drives
it — a fixed 44px row would snap instead of sliding. */
&:has([data-testid='plugins-strip']) {
grid-template-rows: ${WORKBENCH_HEADER_HEIGHT}px auto minmax(0, 1fr);
}
overflow-x: hidden;
overflow-y: hidden;
height: 100%;
`,
workbenchHeader: css2`
display: flex;
align-items: center;
gap: ${semantic.gap.control};
min-width: 0;
height: ${WORKBENCH_HEADER_HEIGHT}px;
/* No trailing gutter: the action icons run to the panel edge. */
padding: 0 0 0 ${WORKBENCH_GUTTER}px;
box-sizing: border-box;
background: ${semantic.color.surface.brand};
color: ${semantic.color.text.mutedOnBrand};
/* A translucent ink rule, not border.decorative — decorative *is* the
cream brand surface, so it disappears on the chrome band itself. */
border-bottom: 1px solid ${semantic.color.state.pressed};
& button {
min-width: 28px;
height: 100%;
box-sizing: border-box;
border: 0;
border-radius: 0;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
}
& button {
transition: all 0.3s ease;
}
& button:hover:not([data-tsd-selected='true']) {
background: ${semantic.color.state.hover};
}
@media (prefers-reduced-motion: reduce) {
& button {
transition: none;
}
}
& button:focus-visible {
outline: 2px solid ${semantic.color.border.focus};
outline-offset: 2px;
}
@media (max-width: 430px) {
gap: ${semantic.gap.tight};
padding-inline-start: ${WORKBENCH_GUTTER_NARROW}px;
& button {
min-width: 24px;
}
}
@media (max-width: 360px) {
gap: 2px;
padding-inline-start: 4px;
& button {
padding-inline: 3px;
font-size: 11px;
}
}
`,
workbenchLogo: css2`
display: inline-flex;
align-items: center;
width: 16px;
height: 21px;
flex: 0 0 16px;
color: ${semantic.color.text.primary};
& > svg {
width: 100%;
height: 100%;
}
@media (max-width: 360px) {
width: 14px;
height: 18px;
flex-basis: 14px;
}
`,
workbenchDestinations: css2`
display: inline-flex;
align-items: stretch;
align-self: stretch;
gap: 0;
/* The destinations are the part that must survive a narrow panel: let
them scroll rather than letting flex squeeze the labels together. */
min-width: 0;
flex: 0 1 auto;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
margin: 0;
padding: 0;
& > button {
flex: 0 0 auto;
}
`,
workbenchNavButton: css2`
margin: 0;
padding-inline: 10px;
font-size: ${semantic.type.bodyXs.size};
font-weight: ${semantic.type.labelSm.weight};
letter-spacing: ${semantic.type.labelSm.tracking};
color: ${semantic.color.text.mutedOnBrand};
&[data-tsd-selected='true'] {
background: ${semantic.color.state.pressed};
color: ${semantic.color.text.primary};
font-weight: 700;
}
@media (max-width: 361px) {
padding-inline: 4px;
}
`,
workbenchActions: css2`
display: inline-flex;
align-items: center;
gap: ${semantic.gap.tight};
height: 100%;
margin-left: auto;
@media (max-width: 360px) {
gap: 0;
}
`,
workbenchActionButton: css2`
display: inline-flex;
align-items: center;
justify-content: center;
width: ${WORKBENCH_HEADER_HEIGHT}px;
min-width: ${WORKBENCH_HEADER_HEIGHT}px;
height: ${WORKBENCH_HEADER_HEIGHT}px;
flex: 0 0 ${WORKBENCH_HEADER_HEIGHT}px;
padding: 0;
color: ${semantic.color.text.mutedOnBrand};
& svg {
width: 20px;
height: 20px;
}
&[data-tsd-selected='true'] {
background: ${semantic.color.state.pressed};
color: ${semantic.color.text.primary};
}
@media (max-width: 360px) {
width: 32px;
min-width: 32px;
flex-basis: 32px;
}
`,
/**
* A pull tab protruding from the bottom edge of the lowest chrome band —
* below the secondary strip when one is on screen, below the header when
* not. It is positioned against the panel rather than nested inside the
* strip, because the strip scrolls horizontally and would clip it.
*
* Collapsed is the exception: the panel is then only as tall as the header
* and sits flush against the viewport edge, so a downward tab would be off
* screen. There it flips to the panel's outer edge instead.
*/
/**
* A pull tab protruding from the bottom edge of the subheader, dropping back
* to the header's bottom edge once the subheader is folded away — so it
* always hangs off whatever chrome band is lowest, always inside the panel.
*
* It is positioned against the panel rather than nested inside the strip,
* because the strip scrolls horizontally and would clip it. Both bands are
* border-box, so their hairlines already sit inside these heights.
*/
workbenchCollapseToggle: (isCollapsed) => css2`
position: absolute;
top: ${isCollapsed ? WORKBENCH_HEADER_HEIGHT : WORKBENCH_HEADER_HEIGHT + PLUGINS_STRIP_HEIGHT}px;
inset-inline-end: 7%;
z-index: 10;
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 20px;
box-sizing: border-box;
padding: 0;
/* Open at the top so it reads as attached to the band above it. */
border: 1px solid ${semantic.color.state.pressed};
border-top: 0;
border-radius: 0 0 ${semantic.radius.group} ${semantic.radius.group};
background: ${semantic.color.surface.brand};
color: ${semantic.color.text.mutedOnBrand};
cursor: pointer;
transition: all 0.3s ease;
&:hover {
height: 24px;
color: ${semantic.color.text.primary};
background: ${semantic.color.surface.subtle};
}
&:focus-visible {
outline: 2px solid ${semantic.color.border.focus};
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
transition: none;
&:hover {
height: 20px;
}
}
`,
workbenchCollapseIcon: css2`
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
transition: transform 0.3s ease;
& svg {
width: 18px;
height: 18px;
}
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
`,
workbenchWordmark: css2`
white-space: nowrap;
/* A wordmark is display type, not body copy. */
font-family: ${semantic.font.display};
font-size: ${semantic.type.headingCompact.size};
font-weight: ${semantic.type.headingCompact.weight};
line-height: ${semantic.type.headingCompact.lineHeight};
letter-spacing: -0.01em;
color: ${semantic.color.text.primary};
margin-inline-end: ${semantic.space[2]};
/* Give up the wordmark before the destination labels start colliding —
the emblem still carries the branding. */
@media (max-width: 560px) {
display: none;
}
`,
/**
* The subheader slides rather than disappearing: it stays mounted and
* animates its own height to zero, and the panel's strip row is auto-sized
* so the row follows it. Folded it is `inert` so nothing inside stays
* focusable behind a zero-height band.
*/
workbenchSecondaryTabs: (collapsed) => css2`
display: flex;
align-items: center;
gap: ${semantic.gap.control};
min-width: 0;
box-sizing: border-box;
padding-block: ${collapsed ? "0px" : "6px"};
padding-inline-start: ${WORKBENCH_GUTTER}px;
padding-inline-end: ${WORKBENCH_GUTTER}px;
scroll-padding-inline-start: ${WORKBENCH_GUTTER}px;
scroll-padding-inline-end: ${WORKBENCH_GUTTER}px;
height: ${collapsed ? 0 : PLUGINS_STRIP_HEIGHT}px;
min-height: 0;
flex: 0 0 auto;
opacity: ${collapsed ? 0 : 1};
/* Chrome band: the strip belongs to the header, not to the canvas. */
background: ${semantic.color.surface.brand};
border-bottom: ${collapsed ? "0" : "1px"} solid
${semantic.color.state.pressed};
overflow-x: ${collapsed ? "hidden" : "auto"};
overflow-y: hidden;
white-space: nowrap;
${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
own hover and selected states. */
& > * {
transform: none;
}
& > :last-child {
scroll-margin-inline-end: ${WORKBENCH_GUTTER}px;
}
@media (max-width: 430px) {
padding-inline: ${WORKBENCH_GUTTER_NARROW}px;
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
workbenchSecondaryTab: css2`
display: inline-flex;
align-items: center;
justify-content: center;
/* A plugin entry can be dragged down into the workspace to place its pane,
so it advertises that rather than looking like a plain button. */
&[data-plugin-title-control] {
cursor: grab;
}
min-height: 32px;
padding: ${semantic.padding.controlBlock}
${semantic.padding.controlInline};
border: 1px solid transparent;
border-radius: ${semantic.radius.control};
background: transparent;
color: ${semantic.color.text.secondary};
font-family: ${semantic.font.body};
font-size: ${semantic.type.labelSm.size};
font-weight: ${semantic.type.labelSm.weight};
line-height: ${semantic.type.labelSm.lineHeight};
letter-spacing: ${semantic.type.labelSm.tracking};
cursor: pointer;
flex: 0 0 auto;
appearance: none;
transition: all 0.3s ease;
&:hover {
background: ${semantic.color.state.hover};
color: ${semantic.color.text.primary};
}
&[data-tsd-selected='true'] {
background: ${semantic.color.state.selectionFill};
border-color: ${semantic.color.state.selectionFill};
color: ${semantic.color.state.selectionText};
}
&:focus-visible {
outline: 2px solid ${semantic.color.border.focus};
outline-offset: 2px;
}
`,
pluginTitleText: css2`
margin: 0;
color: inherit;
font-family: ${semantic.font.body};
font-size: inherit;
font-weight: inherit;
line-height: inherit;
letter-spacing: inherit;
`,
/**
* A thin bar sitting on the panel's own edge. It must NOT be grown into a
* fat hit area: at 24px tall it covered the top of the 36px header, so a
* press aimed at a header button started a resize instead — which is what
* made dragging feel like click, drag, click.
*/
dragHandle: (panelLocation) => css2`
position: absolute;
left: 0;
${panelLocation === "bottom" ? "top" : "bottom"}: 0;
width: 100%;
height: 5px;
cursor: row-resize;
user-select: none;
touch-action: none;
z-index: 100000;
background-color: transparent;
transition: all 0.3s ease;
&:hover,
&:focus-visible {
background-color: ${semantic.color.border.control};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
mainCloseBtn: css2`
background: transparent;
position: fixed;
z-index: 99999;
display: inline-flex;
width: fit-content;
cursor: pointer;
appearance: none;
border: 0;
align-items: center;
padding: 0;
font-size: ${semantic.type.bodyXs.size};
cursor: pointer;
transition: opacity 0.25s ease-out;
&:hide-until-hover {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
&:hide-until-hover:hover {
opacity: 1;
pointer-events: auto;
visibility: visible;
}
`,
mainCloseBtnDefault: css2`
/* 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: 50%;
box-shadow:
inset 0 0 0 1px transparent,
${semantic.shadow.sm};
/* 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 {
display: block;
width: 100%;
height: 100%;
outline: none;
}
/*
* 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.
*
* It animates the scale property rather than a transform: floating mode
* sets transform inline to drive the drag, so a transform here would be
* overridden and never apply.
*/
&:hover {
box-shadow:
inset 0 0 0 1px ${semantic.color.border.control},
${semantic.shadow.overlay};
scale: 1.06;
}
&:active {
scale: 0.98;
}
@media (prefers-reduced-motion: reduce) {
transition-property: opacity;
&:hover,
&:active {
scale: 1;
}
}
&:focus-visible {
outline: 2px solid ${semantic.color.border.focus};
outline-offset: 2px;
}
`,
mainCloseBtnFloating: css2`
/* Floating placement is driven by inline left/top, so don't animate
position (would fight the drag/throw rAF loop). The hover treatment
uses box-shadow and scale, both of which are safe to keep. */
transition:
opacity 0.3s ease,
box-shadow 0.3s ease,
scale 0.3s ease,
background-color 0.3s ease,
color 0.3s ease;
/* Stays a pointer even though it is draggable: the trigger reads as a
button first, and a grab cursor made it look like a handle. */
cursor: pointer;
touch-action: none;
user-select: none;
`,
mainCloseBtnPosition: (position) => {
const base = css2`
${position === "top-left" ? `top: ${semantic.space[2]}; left: ${semantic.space[2]};` : ""}
${position === "top-right" ? `top: ${semantic.space[2]}; right: ${semantic.space[2]};` : ""}
${position === "middle-left" ? `top: 50%; left: ${semantic.space[2]}; transform: translateY(-50%);` : ""}
${position === "middle-right" ? `top: 50%; right: ${semantic.space[2]}; transform: translateY(-50%);` : ""}
${position === "bottom-left" ? `bottom: ${semantic.space[2]}; left: ${semantic.space[2]};` : ""}
${position === "bottom-right" ? `bottom: ${semantic.space[2]}; right: ${semantic.space[2]};` : ""}
`;
return base;
},
mainCloseBtnAnimation: (isOpen, hideUntilHover) => {
if (!isOpen) {
return hideUntilHover ? css2`
opacity: 0;
&:hover {
opacity: 1;
pointer-events: auto;
visibility: visible;
}
` : css2`
opacity: 1;
pointer-events: auto;
visibility: visible;
`;
}
return css2`
opacity: 0;
pointer-events: none;
visibility: hidden;
`;
},
tabContent: css2`
transition: all 0.2s ease-in-out;
width: 100%;
max-width: 100%;
min-width: 0;
height: 100%;
box-sizing: border-box;
overflow-x: hidden;
`,
/**
* A plugin's mount target, and the scroll boundary between that plugin and
* the host page.
*
* `overscroll-behavior: contain` belongs HERE and on the other outermost
* destination scrollers only — never on their descendants. Plugins nest
* several `overflow: auto` wrappers that often have nothing to scroll; a
* wheel over one of those is meant to chain up to this element. Containing
* every descendant turns each empty wrapper into a dead end and the pane
* stops scrolling altogether.
*/
pluginsTabContent: css2`
/*
* A positioning context per pane. Plugins position their own chrome
* absolutely and assume their own root is the containing block, but a
* plugin root is often statically positioned — without this, a top-zero
* offset resolves against the whole Workbench and the plugin paints its
* controls over our header. With three panes open they would all pile
* into the same corner.
*/
position: relative;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
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};
`,
pluginsEmptyState: css2`
display: flex;
flex: 1 1 auto;
flex-direction: column;
align-items: center;
justify-content: center;
gap: ${semantic.gap.control};
min-width: 0;
padding: ${WORKBENCH_GUTTER}px;
text-align: center;
background: ${semantic.color.surface.workspace};
`,
pluginsEmptyStateIcon: css2`
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
margin-bottom: ${semantic.space[1]};
border-radius: 50%;
background: ${semantic.color.surface.subtle};
color: ${semantic.color.text.muted};
& svg {
width: 22px;
height: 22px;
}
`,
pluginsEmptyStateTitle: css2`
margin: 0;
font-family: ${semantic.font.display};
font-size: ${semantic.type.headingPane.size};
font-weight: ${semantic.type.headingPane.weight};
line-height: ${semantic.type.headingPane.lineHeight};
color: ${semantic.color.text.primary};
`,
pluginsEmptyStateHint: css2`
margin: 0;
max-width: 42ch;
font-size: ${semantic.type.bodySm.size};
font-weight: ${semantic.type.bodySm.weight};
line-height: ${semantic.type.bodySm.lineHeight};
color: ${semantic.color.text.secondary};
`,
/**
* The panes' permanent home. Every pane is a direct child for its whole
* life and is placed with inline offsets computed from the layout tree, so
* a drag never re-parents it. That is what stops an iframe plugin reloading
* and a canvas plugin losing its context every time the layout changes.
*/
pluginWorkspace: css2`
position: relative;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
background: ${semantic.color.surface.brand};
`,
/**
* Off-screen but still announced. Used for the live region that narrates
* picking a pane up and putting it down, which is the only feedback a
* screen-reader user gets from a move.
*/
pluginSrOnly: css2`
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
`,
/** A group's tab bar, sitting along the top edge of the group's rect. */
pluginGroupTabs: css2`
display: flex;
align-items: stretch;
gap: ${semantic.gap.tight};
height: ${PLUGIN_GROUP_TAB_HEIGHT}px;
min-width: 0;
padding: ${semantic.space[1]};
box-sizing: border-box;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
${thinScrollbars}
background: ${semantic.color.surface.workspace};
border-radius: ${semantic.radius.overlay} ${semantic.radius.overlay} 0 0;
`,
/**
* Presentational wrapper holding the two sibling controls of one tab. They
* are siblings rather than nested because a button inside a button is
* invalid, and the inner one would be unreachable by keyboard.
*/
pluginGroupTabItem: css2`
position: relative;
display: inline-flex;
align-items: stretch;
flex: 0 0 auto;
max-width: 200px;
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.brand};
}
&:hover:not([data-tsd-selected='true']) {
background: ${semantic.color.state.hover};
}
&[data-tsd-held='true'] {
outline: 2px solid ${semantic.color.border.focus};
outline-offset: -2px;
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
/** The sortable item: exactly the draggable part of a tab, nothing else. */
pluginGroupTabRow: css2`
display: inline-flex;
align-items: stretch;
min-width: 0;
`,
pluginGroupTab: css2`
display: inline-flex;
align-items: center;
min-width: 0;
/* Room at the end for the close button, which sits over this one. */
padding-inline: 8px 28px;
border: 0;
border-radius: 0;
background: transparent;
color: ${semantic.color.text.mutedOnBrand};
font-family: ${semantic.font.body};
font-size: ${semantic.type.bodyXs.size};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: grab;
&[aria-pressed='true'] {
color: ${semantic.color.text.primary};
cursor: default;
}
`,
/**
* 24px square, the smallest target WCAG 2.5.8 accepts, laid over the right end
* of the tab. Positioned rather than in flow so it is a sibling of the
* sortable row: the drag engine finds its target by walking up the tree, so a
* close button nested inside the row would always be a drag surface.
*/
pluginGroupTabClose: css2`
position: absolute;
inset-inline-end: 2px;
top: 50%;
transform: translateY(-50%);
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border: 0;
border-radius: 2px;
background: transparent;
color: ${semantic.color.text.mutedOnBrand};
cursor: pointer;
&:hover {
background: ${semantic.color.state.pressed};
}
& svg {
width: 10px;
height: 10px;
}
`,
/**
* A gutter between two panes of one split. It is a real focusable separator
* so it can be driven from the keyboard, matching the whole-panel resizer.
*/
pluginSplitter: (dir) => css2`
position: absolute;
z-index: 20;
box-sizing: border-box;
background: transparent;
cursor: ${dir === "row" ? "col-resize" : "row-resize"};
touch-action: none;
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) {
&::after {
transition: none;
}
}
@media (forced-colors: active) {
&:hover::after,
&:focus-visible::after {
background: Highlight;
}
}
`,
/**
* The tab that follows the cursor while dragging, so it is obvious which pane
* is being carried. Pointer-transparent, or it would sit between the pointer
* and the drop zone being aimed at.
*/
pluginDragPreview: css2`
position: fixed;
z-index: 2147483646;
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 220px;
height: ${PLUGIN_GROUP_TAB_HEIGHT}px;
padding-inline: 10px;
box-sizing: border-box;
pointer-events: none;
border: 1px solid ${semantic.color.border.focus};
border-radius: 3px;
background: ${semantic.color.surface.brand};
color: ${semantic.color.text.primary};
font-family: ${semantic.font.body};
font-size: ${semantic.type.bodyXs.size};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.95;
/* Sits just off the cursor so it never covers the pointer itself. */
transform: translate(12px, 12px);
@media (forced-colors: active) {
border-color: Highlight;
}
`,
/**
* While a pane is being carried, every surface *inside the panel* shows the
* grabbing cursor — the pointer travels well outside the tab it started on.
* Applied to the panel, never to `<html>`: the host page's cursor is not ours
* to change.
*/
pluginDraggingCursor: css2`
&,
& * {
cursor: grabbing !important;
}
`,
/** The highlight that shows where a dragged tab would land. */
pluginDropOverlay: css2`
position: absolute;
z-index: 3;
pointer-events: none;
box-sizing: border-box;
border: 2px solid ${semantic.color.border.focus};
background: ${semantic.color.state.hover};
@media (forced-colors: active) {
border-color: Highlight;
}
`,
pluginPaneSeparator: css2`
flex: 0 0 1px;
align-self: stretch;
/* Plugins paint their own surface, which may be lighter or darker than
ours, so this rule needs a mid tone that shows against both. */
background: ${semantic.color.border.control};
@media (forced-colors: active) {
background: CanvasText;
}
`,
settingsGroup: css2`
display: flex;
flex-direction: column;
gap: 0.75rem;
`,
/* An indented, ruled block reads as "this belongs to the switch above". */
conditionalSetting: css2`
margin-top: ${semantic.space[2]};
margin-inline-start: ${WORKBENCH_GUTTER}px;
padding: ${semantic.space[3]};
border-inline-start: 2px solid ${semantic.color.border.decorative};
background-color: ${semantic.color.surface.subtle};
border-start-end-radius: ${semantic.radius.group};
border-end-end-radius: ${semantic.radius.group};
`,
settingRow: css2`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
`,
settingsModifiers: css2`
display: flex;
flex-wrap: wrap;
gap: 6px;
`,
hotkeyTitle: css2`
margin: 0;
font-family: ${semantic.font.display};
font-size: ${semantic.type.headingCompact.size};
line-height: ${semantic.type.headingCompact.lineHeight};
font-weight: ${semantic.type.headingCompact.weight};
color: ${semantic.color.text.primary};
`,
hotkeyDescription: css2`
margin: 0;
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodyXs.lineHeight};
color: ${semantic.color.text.secondary};
`,
hotkeyResult: css2`
display: flex;
align-items: center;
gap: 6px;
margin: 0;
font-size: ${semantic.type.bodyXs.size};
color: ${semantic.color.text.secondary};
`,
hotkeyResultKeys: css2`
padding: 1px 6px;
border: 1px solid ${semantic.color.border.decorative};
border-radius: ${semantic.radius.control};
background: ${semantic.color.surface.subtle};
color: ${semantic.color.text.primary};
font-family: ${semantic.font.mono};
font-size: 11px;
`,
settingsStack: css2`
display: flex;
flex-direction: column;
gap: 1rem;
`,
// No Plugins Fallback Styles
/* Shell: positions the settings drawer and clips it to the workbench. */
pluginMarketplace: css2`
position: relative;
display: flex;
flex-direction: column;
font-family: ${semantic.font.body};
color: ${semantic.color.text.primary};
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
height: 100%;
min-height: 0;
overflow: hidden;
background: ${semantic.color.surface.workspace};
animation: ${fadeIn} 0.3s ease;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
`,
pluginMarketplaceScroll: css2`
flex: 1 1 auto;
min-height: 0;
min-width: 0;
box-sizing: border-box;
overflow-y: auto;
overscroll-behavior: contain;
${thinScrollbars}
padding: ${WORKBENCH_GUTTER}px;
@media (max-width: 430px) {
padding: ${WORKBENCH_GUTTER_NARROW}px;
}
`,
pluginMarketplaceHeader: css2`
margin-bottom: ${WORKBENCH_GUTTER}px;
padding-bottom: ${semantic.space[3]};
border-bottom: 1px solid ${semantic.color.border.decorative};
`,
pluginMarketplaceTitleRow: css2`
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: ${WORKBENCH_GUTTER}px;
margin-bottom: 0;
flex-wrap: wrap;
`,
/** Title + description stay together so the search box can't split them. */
pluginMarketplaceTitleBlock: css2`
display: flex;
flex-direction: column;
gap: ${semantic.gap.tight};
min-width: 0;
`,
pluginMarketplaceControls: css2`
display: flex;
align-items: center;
flex: 1 1 320px;
width: 100%;
max-width: 448px;
min-width: 0;
margin-left: auto;
`,
pluginMarketplaceTitle: css2`
font-family: ${semantic.font.display};
font-size: 1.125rem;
line-height: 1.3;
font-weight: 700;
color: ${semantic.color.text.primary};
margin: 0;
letter-spacing: -0.02em;
`,
pluginMarketplaceDescription: css2`
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodyXs.lineHeight};
color: ${semantic.color.text.secondary};
margin: 0;
max-width: 72ch;
`,
pluginMarketplaceSearchWrapper: css2`
position: relative;
display: flex;
align-items: center;
flex: 1 1 0%;
width: auto;
max-width: 400px;
min-width: 0;
@media (max-width: 430px) {
width: 100%;
max-width: none;
}
svg {
position: absolute;
left: 8px;
width: 14px;
height: 14px;
color: ${semantic.color.text.muted};
pointer-events: none;
}
`,
pluginMarketplaceSearch: css2`
width: 100%;
box-sizing: border-box;
padding: 5px 10px 5px 28px;
background: ${semantic.color.surface.app};
border: 1px solid ${semantic.color.border.decorative};
border-radius: ${semantic.radius.control};
color: ${semantic.color.text.primary};
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodyXs.lineHeight};
font-family: ${semantic.font.body};
transition: all 0.3s ease;
&::placeholder {
color: ${semantic.color.text.muted};
}
&:hover {
border-color: ${semantic.color.border.control};
}
&:focus {
outline: none;
border-color: ${semantic.color.border.focus};
background: ${semantic.color.surface.elevated};
box-shadow: 0 0 0 2px ${semantic.color.state.pressed};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
pluginMarketplaceTagsContainer: css2`
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: ${semantic.space[3]};
padding: 0;
background: transparent;
border: 0;
`,
pluginMarketplaceTagButton: css2`
padding: 3px 10px;
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodyXs.lineHeight};
font-weight: 500;
background: ${semantic.color.surface.subtle};
border: 1px solid ${semantic.color.border.decorative};
border-radius: 999px;
color: ${semantic.color.text.secondary};
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: ${semantic.color.state.hover};
border-color: ${semantic.color.border.control};
color: ${semantic.color.text.primary};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
pluginMarketplaceTagButtonActive: css2`
background: ${semantic.color.state.selectionFill} !important;
border-color: ${semantic.color.state.selectionFill} !important;
color: ${semantic.color.state.selectionText} !important;
&:hover {
background: ${semantic.color.state.selectionFill} !important;
border-color: ${semantic.color.border.focus} !important;
}
`,
pluginMarketplaceSettingsButton: css2`
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
padding: 5px;
background: ${semantic.color.surface.subtle};
border: 1px solid ${semantic.color.border.decorative};
border-radius: ${semantic.radius.control};
color: ${semantic.color.text.secondary};
cursor: pointer;
transition: all 0.3s ease;
margin-left: 6px;
& svg {
width: 14px;
height: 14px;
}
&:hover {
background: ${semantic.color.state.hover};
border-color: ${semantic.color.border.control};
color: ${semantic.color.text.primary};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
/* Absolute, not fixed: the drawer belongs to the marketplace pane, so it
must not cover the host page outside the workbench. */
pluginMarketplaceSettingsPanel: css2`
position: absolute;
inset-block: 0;
inset-inline-end: 0;
width: 320px;
max-width: 100%;
box-sizing: border-box;
background: ${semantic.color.surface.elevated};
border-inline-start: 1px solid ${semantic.color.border.decorative};
box-shadow: ${semantic.shadow.overlay};
z-index: 2;
display: flex;
flex-direction: column;
animation: ${slideInRight} 0.3s ease;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
`,
pluginMarketplaceSettingsPanelHeader: css2`
display: flex;
align-items: center;
justify-content: space-between;
gap: ${semantic.gap.control};
padding: ${semantic.space[3]} ${WORKBENCH_GUTTER}px;
border-bottom: 1px solid ${semantic.color.border.decorative};
`,
pluginMarketplaceSettingsPanelTitle: css2`
font-family: ${semantic.font.display};
font-size: ${semantic.type.headingCompact.size};
line-height: ${semantic.type.headingCompact.lineHeight};
font-weight: 700;
color: ${semantic.color.text.primary};
margin: 0;
`,
pluginMarketplaceSettingsPanelClose: css2`
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
background: transparent;
border: none;
color: ${semantic.color.text.secondary};
cursor: pointer;
border-radius: 0.375rem;
transition: all 0.3s ease;
&:hover {
background: ${semantic.color.state.hover};
color: ${semantic.color.text.primary};
}
`,
pluginMarketplaceSettingsPanelContent: css2`
flex: 1;
min-height: 0;
padding: ${WORKBENCH_GUTTER}px;
overflow-y: auto;
overscroll-behavior: contain;
${thinScrollbars}
`,
pluginMarketplaceGrid: css2`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
gap: ${semantic.gap.section};
animation: ${slideUp} 0.4s ease;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
`,
pluginMarketplaceCard: css2`
background: ${semantic.color.surface.elevated};
border: 1px solid ${semantic.color.border.decorative};
border-radius: ${semantic.radius.overlay};
padding: ${WORKBENCH_GUTTER}px;
display: flex;
flex-direction: column;
gap: ${semantic.gap.section};
transition: all 0.3s ease;
position: relative;
overflow: hidden;
/* Cards stay grounded: hover raises the shadow a step instead of lifting
the card off the page. */
&:hover {
border-color: ${semantic.color.border.control};
box-shadow: ${semantic.shadow.sm};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
pluginMarketplaceCardIcon: css2`
width: 32px;
height: 32px;
flex: 0 0 32px;
display: flex;
align-items: center;
justify-content: center;
background: ${semantic.color.surface.subtle};
border: 1px solid ${semantic.color.border.decorative};
border-radius: ${semantic.radius.group};
color: ${semantic.color.text.secondary};
svg {
width: 16px;
height: 16px;
}
`,
pluginMarketplaceCardHeader: css2`
flex: 1;
`,
pluginMarketplaceCardTitle: css2`
font-family: ${semantic.font.display};
font-size: ${semantic.type.bodySm.size};
line-height: ${semantic.type.bodySm.lineHeight};
font-weight: 700;
color: ${semantic.color.text.primary};
/* Room on the trailing side so a long name never runs under the badge. */
margin: 0 72px 4px 0;
`,
pluginMarketplaceCardPackageBadge: css2`
margin-top: 4px;
margin-bottom: 8px;
font-size: 0.6875rem;
font-family: ${semantic.font.mono};
color: ${semantic.color.text.muted};
padding: 0;
word-break: break-all;
display: inline-block;
`,
pluginMarketplaceCardDescriptionText: css2`
margin-top: 0;
font-size: ${semantic.type.bodyXs.size};
line-height: ${semantic.type.bodySm.lineHeight};
color: ${semantic.color.text.secondary};
`,
pluginMarketplaceCardVersionInfo: css2`
margin-top: 8px;
font-size: 0.6875rem;
font-family: ${semantic.font.mono};
`,
pluginMarketplaceCardVersionSatisfied: css2`
color: ${semantic.color.status.success.text};
`,
pluginMarketplaceCardVersionUnsatisfied: css2`
color: ${semantic.color.status.error.text};
`,
pluginMarketplaceCardDocsLink: css2`
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: ${semantic.color.text.link};
text-decoration: none;
margin-top: 0.5rem;
transition: all 0.3s ease;
&:hover {
color: ${semantic.color.text.link};
text-decoration: underline;
}
svg {
width: 12px;
height: 12px;
}
`,
pluginMarketplaceCardTags: css2`
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
margin-top: 0.75rem;
`,
pluginMarketplaceCardTag: css2`
font-size: 0.6875rem;
font-weight: 500;
padding: 0.25rem 0.5rem;
background: ${semantic.color.surface.subtle};
border: 1px solid ${semantic.color.border.decorative};
border-radius: 999px;
color: ${semantic.color.text.secondary};
`,
pluginMarketplaceCardImage: css2`
width: 28px;
height: 28px;
object-fit: contain;
`,
/* A flat inline pill, not a rotated corner ribbon: the badge already owns
the top-right corner and the icon owns the top-left. */
pluginMarketplaceNewBanner: css2`
display: inline-block;
vertical-align: middle;
margin-inline-start: 6px;
background-color: ${semantic.color.status.success.subtleFill};
color: ${semantic.color.status.success.text};
padding: 1px 6px;
font-family: ${semantic.font.body};
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
border-radius: 999px;
letter-spacing: 0.05em;
`,
/* Featured and active cards keep the neutral outline — their section
heading and their badge already say which they are, so a second and a
third accent colour would only add noise. */
pluginMarketplaceCardFeatured: css2`
border-color: ${semantic.color.border.control};
`,
pluginMarketplaceCardActive: css2`
border-inline-start: 3px solid ${semantic.color.status.success.border};
`,
pluginMarketplaceCardStatus: css2`
display: flex;
align-items: center;
gap: 0.5rem;
color: ${semantic.color.status.success.text};
animation: ${statusFadeIn} 0.3s ease;
svg {
width: 18px;
height: 18px;
animation: ${statusFadeIn} 120ms ease-out;
}
`,
pluginMarketplaceCardSpinner: css2`
width: 18px;
height: 18px;
border: 2px solid ${semantic.color.border.decorative};
border-top-color: ${semantic.color.status.info.border};
border-radius: 50%;
animation: ${spin} 0.8s linear infinite;
`,
pluginMarketplaceCardStatusText: css2`
font-size: 0.875rem;
font-weight: 600;
`,
pluginMarketplaceCardStatusTextError: css2`
font-size: 0.875rem;
font-weight: 600;
color: ${semantic.color.status.error.text};
`,
pluginMarketplaceEmpty: css2`
padding: 3rem 2rem;
text-align: center;
background: ${semantic.color.surface.elevated};
border: 2px dashed ${semantic.color.border.control};
border-radius: 0.75rem;
animation: ${fadeIn} 0.3s ease;
`,
pluginMarketplaceEmptyText: css2`
font-size: 0.95rem;
color: ${semantic.color.text.secondary};
margin: 0;
line-height: 1.6;
`,
// Framework sections
pluginMarketplaceSection: css2`
margin-bottom: ${WORKBENCH_GUTTER * 1.5}px;
&:last-child {
margin-bottom: 0;
}
`,
/* A section heading is a heading, not a card. It gets a rule underneath so
the eye reads "group starts here" without another box in the stack. */
pluginMarketplaceSectionHeader: css2`
margin-bottom: ${semantic.gap.section};
padding: 0 0 6px;
display: flex;
align-items: center;
gap: ${semantic.gap.tight};
cursor: pointer;
user-select: none;
background: transparent;
border: 0;
border-bottom: 1px solid ${semantic.color.border.decorative};
border-radius: 0;
transition: all 0.3s ease;
&:hover {
border-bottom-color: ${semantic.color.border.control};
}
&:hover h3 {
color: ${semantic.color.text.primary};
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
pluginMarketplaceSectionContent: css2`
display: flex;
flex-direction: column;
gap: ${semantic.gap.sectionLarge};
`,
pluginMarketplaceSectionHeaderLeft: css2`
display: flex;
align-items: center;
gap: 0.5rem;
`,
pluginMarketplaceSectionChevron: css2`
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
color: ${semantic.color.text.secondary};
transition: transform 0.2s ease;
`,
pluginMarketplaceSectionChevronCollapsed: css2`
transform: rotate(-90deg);
`,
pluginMarketplaceSectionTitle: css2`
font-family: ${semantic.font.display};
font-size: ${semantic.type.headingPane.size};
line-height: ${semantic.type.headingPane.lineHeight};
font-weight: 700;
color: ${semantic.color.text.secondary};
margin: 0;
display: flex;
align-items: center;
gap: ${semantic.gap.control};
transition: all 0.3s ease;
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
pluginMarketplaceFeatureBanner: css2`
margin-top: 0;
padding: ${semantic.space[3]} ${WORKBENCH_GUTTER}px;
background: ${semantic.color.surface.brand};
border-radius: ${semantic.radius.overlay};
border: 1px solid ${semantic.color.border.decorative};
border-inline-start: 3px solid ${semantic.color.state.selectionFill};
box-shadow: none;
`,
pluginMarketplaceFeatureBannerContent: css2`
display: flex;
flex-direction: column;
gap: ${semantic.gap.control};
align-items: flex-start;
`,
pluginMarketplaceFeatureBannerTitle: css2`
font-family: ${semantic.font.display};
font-size: ${semantic.type.headingCompact.size};
line-height: ${semantic.type.headingCompact.lineHeight};
font-weight: 700;
color: ${semantic.color.text.primary};
margin: 0;
display: flex;
align-items: center;
gap: 6px;
`,
pluginMarketplaceFeatureBannerIcon: css2`
width: 14px;
height: 14px;
display: inline-flex;
color: ${semantic.color.text.secondary};
`,
pluginMarketplaceFeatureBannerText: css2`
font-size: ${semantic.type.bodyXs.size};
color: ${semantic.color.text.mutedOnBrand};
line-height: ${semantic.type.bodySm.lineHeight};
max-width: 78ch;
margin: 0;
`,
pluginMarketplaceFeatureBannerButton: css2`
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
background: ${semantic.color.state.selectionFill};
color: ${semantic.color.state.selectionText};
font-weight: 600;
font-size: ${semantic.type.bodyXs.size};
border-radius: ${semantic.radius.control};
border: none;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
align-self: flex-start;
box-shadow: none;
&:hover {
opacity: 0.85;
}
&:focus-visible {
outline: 2px solid ${semantic.color.border.focus};
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`,
pluginMarketplaceFeatureBannerButtonIcon: css2`
width: 14px;
height: 14px;
`,
pluginMarketplaceCardDisabled: css2`
opacity: 0.6;
filter: grayscale(0.3);
cursor: not-allowed;
&:hover {
transform: none;
box-shadow: none;
}
`,
// Card state badges
pluginMarketplaceCardBadge: css2`
position: absolute;
top: ${WORKBENCH_GUTTER}px;
right: ${WORKBENCH_GUTTER}px;
padding: 1px 6px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
border-radius: 999px;
letter-spacing: 0.05em;
`,
pluginMarketplaceCardBadgeInstall: css2`
background: ${semantic.color.status.success.subtleFill};
color: ${semantic.color.status.success.text};
`,
pluginMarketplaceCardBadgeActive: css2`
background: ${semantic.color.status.success.subtleFill};
color: ${semantic.color.status.success.text};
`,
pluginMarketplaceCardBadgeAdd: css2`
background: ${semantic.color.status.info.subtleFill};
color: ${semantic.color.status.info.text};
`,
pluginMarketplaceCardBadgeBlocked: css2`
background: ${semantic.color.status.warning.subtleFill};
color: ${semantic.color.status.warning.text};
`,
pluginMarketplaceCardBadgeRequires: css2`
background: ${semantic.color.status.neutral.subtleFill};
color: ${semantic.color.status.neutral.text};
`,
// Button style for already installed plugins
pluginMarketplaceButtonInstalled: css2`
opacity: 0.5;
`
};
};
function createStyles() {
const { theme } = createTheme();
const [styles, setStyles] = createSignal(stylesFactory(theme()));
createEffect(() => {
setStyles(stylesFactory(theme()));
});
return styles;
}
var _tmpl$ = /* @__PURE__ */ template(`<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-opacity=0.99></circle><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=#171717></path><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=#171717></path><defs><linearGradient x1=8 y1=0 x2=8 y2=18 gradientUnits=userSpaceOnUse><stop stop-color=#FF5F5F></stop><stop offset=0.344449 stop-color=#FFA05C></stop><stop offset=0.733354 stop-color=#FFF27C></stop><stop offset=1 stop-color=#74DCFF>`);
var TanStackTriggerMark = () => {
const gradientId = `tsd-trigger-mark-${createUniqueId()}`;
return (() => {
var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling; _el$9.nextSibling;
setAttribute(_el$2, "fill", `url(#${gradientId})`);
setAttribute(_el$6, "id", gradientId);
return _el$;
})();
};
// src/components/trigger.tsx
var _tmpl$2 = /* @__PURE__ */ template(`<div>`);
var _tmpl$22 = /* @__PURE__ */ template(`<button type=button data-tsd-control aria-label="Open TanStack Devtools">`);
var FRICTION = 0.95;
var RESTITUTION = 0.5;
var MIN_SPEED = 0.1;
var DRAG_THRESHOLD = 4;
var PADDING_RATIO = 0.5;
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
var stepAxis = (pos, vel, min, max) => {
let p = pos + vel;
let v = vel * FRICTION;
if (p <= min) {
p = min;
v = -v * RESTITUTION;
} else if (p >= max) {
p = max;
v = -v * RESTITUTION;
}
return {
pos: p,
vel: v
};
};
var Trigger = (props) => {
const {
settings,
setSettings
} = createDevtoolsSettings();
const [containerRef, setContainerRef] = createSignal();
const [buttonRef, setButtonRef] = createSignal();
const [coords, setCoords] = createSignal(settings().triggerCoords ?? null);
const styles = createStyles();
const isFloating = createMemo(() => settings().triggerMode === "floating");
const buttonStyle = createMemo(() => {
return clsx(
styles().mainCloseBtn,
// Keep the fixed-position class until floating coords are seeded, so the
// seed reads the trigger's real on-screen spot (not the unpositioned
// static-flow position) and the hand-off to inline left/top is seamless.
(!isFloating() || !coords()) && styles().mainCloseBtnPosition(settings().position),
!settings().customTrigger && styles().mainCloseBtnDefault,
styles().mainCloseBtnAnimation(props.isOpen(), settings().hideUntilHover),
isFloating() && styles().mainCloseBtnFloating
);
});
const edgePadding = (el) => {
const fontSize = parseFloat(getComputedStyle(el).getPropertyValue("--tsrd-font-size"));
return (Number.isFinite(fontSize) ? fontSize : 16) * PADDING_RATIO;
};
const bounds = (el) => {
const pad = edgePadding(el);
const rect = el.getBoundingClientRect();
return {
minX: pad,
minY: pad,
maxX: window.innerWidth - rect.width - pad,
maxY: window.innerHeight - rect.height - pad
};
};
let dragging = false;
let moved = false;
let startX = 0;
let startY = 0;
let startPosX = 0;
let startPosY = 0;
let lastX = 0;
let lastY = 0;
let lastT = 0;
let vx = 0;
let vy = 0;
let raf;
const cancelThrow = () => {
if (raf !== void 0) {
cancelAnimationFrame(raf);
raf = void 0;
}
};
const persist = () => setSettings({
triggerCoords: coords() ?? void 0
});
const startThrow = () => {
cancelThrow();
const tick = () => {
const el = buttonRef();
const current = coords();
if (!el || !current) {
raf = void 0;
return;
}
const b = bounds(el);
const nx = stepAxis(current.x, vx, b.minX, b.maxX);
const ny = stepAxis(current.y, vy, b.minY, b.maxY);
vx = nx.vel;
vy = ny.vel;
setCoords({
x: nx.pos,
y: ny.pos
});
if (Math.hypot(vx, vy) > MIN_SPEED) {
raf = requestAnimationFrame(tick);
} else {
raf = void 0;
persist();
}
};
raf = requestAnimationFrame(tick);
};
const onPointerDown = (e) => {
if (!isFloating() || e.button !== 0) return;
const el = buttonRef();
const current = coords();
if (!el || !current) return;
cancelThrow();
dragging = true;
moved = false;
el.setPointerCapture(e.pointerId);
startX = e.clientX;
startY = e.clientY;
startPosX = current.x;
startPosY = current.y;
lastX = e.clientX;
lastY = e.clientY;
lastT = e.timeStamp;
vx = 0;
vy = 0;
e.preventDefault();
};
const onPointerMove = (e) => {
if (!dragging) return;
e.preventDefault();
const el = buttonRef();
if (!el) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (Math.hypot(dx, dy) > DRAG_THRESHOLD) moved = true;
const b = bounds(el);
setCoords({
x: clamp(startPosX + dx, b.minX, b.maxX),
y: clamp(startPosY + dy, b.minY, b.maxY)
});
const dt = e.timeStamp - lastT;
if (dt > 0) {
vx = (e.clientX - lastX) / dt * 16;
vy = (e.clientY - lastY) / dt * 16;
}
lastX = e.clientX;
lastY = e.clientY;
lastT = e.timeStamp;
};
const endDrag = (e, canThrow) => {
if (!dragging) return;
dragging = false;
const el = buttonRef();
if (el?.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);
if (e.timeStamp - lastT > 50) {
vx = 0;
vy = 0;
}
if (canThrow && moved && Math.hypot(vx, vy) > MIN_SPEED) {
startThrow();
} else {
persist();
}
};
const onPointerUp = (e) => endDrag(e, true);
const onPointerCancel = (e) => endDrag(e, false);
const onClick = () => {
if (moved) {
moved = false;
return;
}
props.setIsOpen(!props.isOpen());
};
createEffect(() => {
if (!isFloating()) return;
const el = buttonRef();
if (!el) return;
untrack(() => {
const current = coords();
if (!current) {
const rect = el.getBoundingClientRect();
setCoords({
x: rect.left,
y: rect.top
});
return;
}
const b = bounds(el);
setCoords({
x: clamp(current.x, b.minX, b.maxX),
y: clamp(current.y, b.minY, b.maxY)
});
});
});
createEffect(() => {
if (!isFloating()) return;
const onResize = () => {
const el = buttonRef();
const current = coords();
if (!el || !current) return;
const b = bounds(el);
setCoords({
x: clamp(current.x, b.minX, b.maxX),
y: clamp(current.y, b.minY, b.maxY)
});
persist();
};
window.addEventListener("resize", onResize);
onCleanup(() => window.removeEventListener("resize", onResize));
});
onCleanup(cancelThrow);
createEffect(() => {
const triggerComponent = settings().customTrigger;
const el = containerRef();
if (triggerComponent && el) {
triggerComponent(el, {
theme: settings().theme
});
}
});
return createComponent(Show, {
get when() {
return !settings().triggerHidden;
},
get children() {
var _el$ = _tmpl$22();
_el$.addEventListener("pointercancel", onPointerCancel);
_el$.$$pointerup = onPointerUp;
_el$.$$pointermove = onPointerMove;
_el$.$$pointerdown = onPointerDown;
_el$.$$click = onClick;
use(setButtonRef, _el$);
insert(_el$, createComponent(Show, {
get when() {
return settings().customTrigger;
},
get fallback() {
return createComponent(TanStackTriggerMark, {});
},
get children() {
var _el$2 = _tmpl$2();
use(setContainerRef, _el$2);
return _el$2;
}
}));
effect((_p$) => {
var _v$ = buttonStyle(), _v$2 = isFloating() && coords() ? {
left: `${coords().x}px`,
top: `${coords().y}px`,
right: "auto",
bottom: "auto",
transform: "none"
} : void 0;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_p$.t = style(_el$, _v$2, _p$.t);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
}
});
};
delegateEvents(["click", "pointerdown", "pointermove", "pointerup"]);
var _tmpl$3 = /* @__PURE__ */ template(`<nav data-workbench-secondary-tabs data-tsd-surface>`);
var _tmpl$23 = /* @__PURE__ */ template(`<button type=button data-testid=workbench-collapse-toggle data-tsd-control><span aria-hidden=true>`);
var _tmpl$32 = /* @__PURE__ */ template(`<button type=button data-workbench-secondary-tab data-tsd-control>`);
var WorkbenchSecondaryTabs = (props) => {
const styles = createStyles();
const {
isCollapsed
} = createCollapsed();
return (() => {
var _el$ = _tmpl$3();
insert(_el$, () => props.children);
effect((_p$) => {
var _v$ = props.ariaLabel, _v$2 = props.dataTestId, _v$3 = isCollapsed() ? "true" : void 0, _v$4 = isCollapsed() || void 0, _v$5 = isCollapsed() ? "true" : void 0, _v$6 = `${styles().workbenchSecondaryTabs(isCollapsed())} tsd-workbench-secondary-tabs`;
_v$ !== _p$.e && setAttribute(_el$, "aria-label", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$, "data-testid", _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$, "data-collapsed", _p$.a = _v$3);
_v$4 !== _p$.o && (_el$.inert = _p$.o = _v$4);
_v$5 !== _p$.i && setAttribute(_el$, "aria-hidden", _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$, _p$.n = _v$6);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$;
})();
};
var CollapseToggle = () => {
const styles = createStyles();
const {
isCollapsed,
toggleCollapsed
} = createCollapsed();
const label = () => `${isCollapsed() ? "Show" : "Hide"} the plugin and section tabs`;
return (() => {
var _el$2 = _tmpl$23(), _el$3 = _el$2.firstChild;
addEventListener(_el$2, "click", toggleCollapsed, true);
insert(_el$3, createComponent(ChevronDownIcon, {}));
effect((_p$) => {
var _v$7 = label(), _v$8 = label(), _v$9 = !isCollapsed(), _v$0 = styles().workbenchCollapseToggle(isCollapsed()), _v$1 = styles().workbenchCollapseIcon, _v$10 = isCollapsed() ? "rotate(0deg)" : "rotate(180deg)";
_v$7 !== _p$.e && setAttribute(_el$2, "aria-label", _p$.e = _v$7);
_v$8 !== _p$.t && setAttribute(_el$2, "title", _p$.t = _v$8);
_v$9 !== _p$.a && setAttribute(_el$2, "aria-expanded", _p$.a = _v$9);
_v$0 !== _p$.o && className(_el$2, _p$.o = _v$0);
_v$1 !== _p$.i && className(_el$3, _p$.i = _v$1);
_v$10 !== _p$.n && setStyleProperty(_el$3, "transform", _p$.n = _v$10);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$2;
})();
};
var WorkbenchSecondaryTab = (props) => {
const styles = createStyles();
return (() => {
var _el$4 = _tmpl$32();
addEventListener(_el$4, "click", props.onClick, true);
_el$4.$$pointerdown = (event) => props.onPointerDown?.(event);
_el$4.addEventListener("focus", (event) => event.currentTarget.scrollIntoView({
block: "nearest",
inline: "nearest"
}));
insert(_el$4, () => props.children);
effect((_p$) => {
var _v$11 = props.ariaCurrent, _v$12 = props.ariaPressed, _v$13 = props.ariaLabelledBy, _v$14 = props.pluginTitleControl ? "" : void 0, _v$15 = props.selected ? "true" : void 0, _v$16 = styles().workbenchSecondaryTab;
_v$11 !== _p$.e && setAttribute(_el$4, "aria-current", _p$.e = _v$11);
_v$12 !== _p$.t && setAttribute(_el$4, "aria-pressed", _p$.t = _v$12);
_v$13 !== _p$.a && setAttribute(_el$4, "aria-labelledby", _p$.a = _v$13);
_v$14 !== _p$.o && setAttribute(_el$4, "data-plugin-title-control", _p$.o = _v$14);
_v$15 !== _p$.i && setAttribute(_el$4, "data-tsd-selected", _p$.i = _v$15);
_v$16 !== _p$.n && className(_el$4, _p$.n = _v$16);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$4;
})();
};
delegateEvents(["click", "pointerdown"]);
// src/components/main-panel.tsx
var _tmpl$4 = /* @__PURE__ */ template(`<div data-testid=tanstack-devtools-panel data-tsd-surface style=max-inline-size:100%;inset-inline:0px;box-sizing:border-box><div data-testid=devtools-drawer-content>`);
var MainPanel = (props) => {
const styles = createStyles();
const {
height
} = createHeight();
const {
settings
} = createDevtoolsSettings();
const pip = createPiPWindow();
const panelWindow = () => pip().pipWindow ?? (typeof window === "undefined" ? null : window);
const readClientWidth = () => {
const targetWindow = panelWindow();
if (!targetWindow) return 0;
return targetWindow.document.documentElement.clientWidth || targetWindow.innerWidth;
};
const [clientWidth, setClientWidth] = createSignal(readClientWidth());
createEffect(() => {
const targetWindow = panelWindow();
if (!targetWindow) return;
const targetRoot = targetWindow.document.documentElement;
const syncClientWidth = () => {
setClientWidth(targetRoot.clientWidth || targetWindow.innerWidth);
};
syncClientWidth();
targetWindow.addEventListener("resize", syncClientWidth);
const ResizeObserverConstructor = targetWindow.ResizeObserver ?? globalThis.ResizeObserver;
const resizeObserver = ResizeObserverConstructor ? new ResizeObserverConstructor(syncClientWidth) : null;
resizeObserver?.observe(targetRoot);
onCleanup(() => {
targetWindow.removeEventListener("resize", syncClientWidth);
resizeObserver?.disconnect();
});
});
const isAttached = () => pip().pipWindow === null;
const panelHeight = () => pip().pipWindow ? "100vh" : `${height()}px`;
const translation = () => {
if (!isAttached() || props.isOpen()) return "translateY(0px)";
return settings().panelLocation === "top" ? "translateY(-100%)" : "translateY(100%)";
};
return (() => {
var _el$ = _tmpl$4(), _el$2 = _el$.firstChild;
setAttribute(_el$, "id", TANSTACK_DEVTOOLS);
insert(_el$2, () => props.children);
insert(_el$, (() => {
var _c$ = memo(() => !!props.hasSubheader());
return () => _c$() ? createComponent(CollapseToggle, {}) : null;
})(), null);
effect((_p$) => {
var _v$ = String(props.isOpen()), _v$2 = String(props.isCollapsed()), _v$3 = panelHeight(), _v$4 = isAttached() && clientWidth() > 0 ? `${clientWidth()}px` : "100%", _v$5 = translation(), _v$6 = panelHeight(), _v$7 = clsx(styles().devtoolsPanelContainer(settings().panelLocation, Boolean(pip().pipWindow)), styles().devtoolsPanelContainerVisibility(props.isOpen()), styles().devtoolsPanelContainerResizing(props.isResizing)), _v$8 = styles().devtoolsDrawerContent;
_v$ !== _p$.e && setAttribute(_el$, "data-open", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$, "data-subheader-collapsed", _p$.t = _v$2);
_v$3 !== _p$.a && setStyleProperty(_el$, "height", _p$.a = _v$3);
_v$4 !== _p$.o && setStyleProperty(_el$, "inline-size", _p$.o = _v$4);
_v$5 !== _p$.i && setStyleProperty(_el$, "transform", _p$.i = _v$5);
_v$6 !== _p$.n && setStyleProperty(_el$, "--tsd-main-panel-height", _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$2, _p$.h = _v$8);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0
});
return _el$;
})();
};
var _tmpl$5 = /* @__PURE__ */ template(`<div data-testid=tanstack-devtools-content-panel data-tsd-surface style=flex-direction:column>`);
var _tmpl$24 = /* @__PURE__ */ template(`<div data-testid=tsd-resize-handle data-tsd-control data-tsd-separator=resize role=separator aria-orientation=horizontal aria-label="Resize TanStack Devtools panel"tabindex=0>`);
var ContentPanel = (props) => {
const styles = createStyles();
const {
settings
} = createDevtoolsSettings();
const {
height
} = createHeight();
const pip = createPiPWindow();
const maxHeight = () => Math.floor(window.innerHeight * PANEL_MAX_VIEWPORT_RATIO);
const clampedHeight = () => Math.min(maxHeight(), Math.max(PANEL_CLOSE_THRESHOLD, Math.round(height())));
const resizeWithKeyboard = (event) => {
const step = event.shiftKey ? 50 : 10;
const grows = settings().panelLocation === "bottom" && event.key === "ArrowUp" || settings().panelLocation === "top" && event.key === "ArrowDown";
const shrinks = settings().panelLocation === "bottom" && event.key === "ArrowDown" || settings().panelLocation === "top" && event.key === "ArrowUp";
let nextHeight;
if (grows) nextHeight = clampedHeight() + step;
if (shrinks) nextHeight = clampedHeight() - step;
if (event.key === "Home") nextHeight = PANEL_CLOSE_THRESHOLD;
if (event.key === "End") nextHeight = maxHeight();
if (nextHeight === void 0) return;
event.preventDefault();
props.handleHeightChange?.(Math.min(maxHeight(), nextHeight));
};
return (() => {
var _el$ = _tmpl$5();
var _ref$ = props.ref;
typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$;
insert(_el$, (() => {
var _c$ = memo(() => !!(props.handleDragStart && pip().pipWindow === null));
return () => _c$() ? (() => {
var _el$2 = _tmpl$24();
_el$2.$$keydown = resizeWithKeyboard;
addEventListener(_el$2, "mousedown", props.handleDragStart, true);
setAttribute(_el$2, "aria-valuemin", PANEL_CLOSE_THRESHOLD);
effect((_p$) => {
var _v$ = styles().dragHandle(settings().panelLocation), _v$2 = maxHeight(), _v$3 = clampedHeight();
_v$ !== _p$.e && className(_el$2, _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$2, "aria-valuemax", _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$2, "aria-valuenow", _p$.a = _v$3);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$2;
})() : null;
})(), null);
insert(_el$, () => props.children, null);
effect(() => className(_el$, styles().devtoolsPanel));
return _el$;
})();
};
delegateEvents(["mousedown", "keydown"]);
var _tmpl$6 = /* @__PURE__ */ template(`<div><h4></h4><p></p><div></div><p>Final shortcut is <kbd>`);
var MODIFIER_DISPLAY_NAMES = {
Shift: "Shift",
Alt: "Alt",
Meta: "Meta",
Control: "Control",
CtrlOrMeta: "Ctrl Or Meta"
};
var HotkeyConfig = (props) => {
const styles = createStyles();
const toggleModifier = (modifier) => {
if (props.hotkey.includes(modifier)) {
props.onHotkeyChange(props.hotkey.filter((key) => key !== modifier));
} else {
const existingModifiers = props.hotkey.filter((key) => props.modifiers.includes(key));
const otherKeys = props.hotkey.filter((key) => !props.modifiers.includes(key));
props.onHotkeyChange([...existingModifiers, modifier, ...otherKeys]);
}
};
const getNonModifierValue = () => {
return props.hotkey.filter((key) => !props.modifiers.includes(key)).join("+");
};
const handleKeyInput = (input) => {
const makeModifierArray = (key) => {
if (key.length === 1) return [uppercaseFirstLetter(key)];
const modifiersArray = [];
for (const character of key) {
const newLetter = uppercaseFirstLetter(character);
if (!modifiersArray.includes(newLetter)) modifiersArray.push(newLetter);
}
return modifiersArray;
};
const hotkeyModifiers = props.hotkey.filter((key) => props.modifiers.includes(key));
const newKeys = input.split("+").flatMap((key) => makeModifierArray(key)).filter(Boolean);
props.onHotkeyChange([...hotkeyModifiers, ...newKeys]);
};
const getDisplayHotkey = () => {
return props.hotkey.join(" + ");
};
return (() => {
var _el$ = _tmpl$6(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.nextSibling;
insert(_el$2, () => props.title);
insert(_el$3, () => props.description);
insert(_el$4, createComponent(Show, {
keyed: true,
get when() {
return props.hotkey;
},
get children() {
return props.modifiers.map((modifier) => {
const enabled = props.hotkey.includes(modifier);
return createComponent(Button, {
variant: "secondary",
"aria-pressed": enabled,
onclick: () => toggleModifier(modifier),
outline: !enabled,
get children() {
return MODIFIER_DISPLAY_NAMES[modifier] || modifier;
}
});
});
}
}));
insert(_el$, createComponent(Input, {
label: "Key",
description: "Use '+' to combine keys (e.g., 'a+b' or 'd'). This will be used with the enabled modifiers from above",
placeholder: "a",
get value() {
return getNonModifierValue();
},
onChange: handleKeyInput
}), _el$5);
insert(_el$8, getDisplayHotkey);
effect((_p$) => {
var _v$ = styles().settingsGroup, _v$2 = styles().hotkeyTitle, _v$3 = styles().hotkeyDescription, _v$4 = styles().settingsModifiers, _v$5 = styles().hotkeyResult, _v$6 = styles().hotkeyResultKeys;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$8, _p$.n = _v$6);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$;
})();
};
// src/tabs/settings-tab.tsx
var _tmpl$7 = /* @__PURE__ */ template(`<div>`);
var _tmpl$25 = /* @__PURE__ */ template(`<div><div>`);
var _tmpl$33 = /* @__PURE__ */ template(`<div data-testid=settings-workspace data-tsd-surface style=height:100%;min-height:0px>`);
var SettingsTab = () => {
const {
setSettings,
settings
} = createDevtoolsSettings();
const styles = createStyles();
const modifiers = ["CtrlOrMeta", "Alt", "Shift"];
return (() => {
var _el$ = _tmpl$33();
insert(_el$, createComponent(MainPanel$1, {
withPadding: true,
get children() {
return [createComponent(Section, {
get children() {
return [createComponent(SectionTitle, {
get children() {
return [createComponent(SectionIcon, {
get children() {
return createComponent(SettingsCog, {});
}
}), "General"];
}
}), createComponent(SectionDescription, {
children: "Configure general behavior of the devtools panel."
}), (() => {
var _el$2 = _tmpl$7();
insert(_el$2, createComponent(Checkbox, {
label: "Default open",
description: "Automatically open the devtools panel when the page loads",
onChange: () => setSettings({
defaultOpen: !settings().defaultOpen
}),
get checked() {
return settings().defaultOpen;
}
}), null);
insert(_el$2, createComponent(Checkbox, {
label: "Hide trigger until hovered",
description: "Keep the devtools trigger button hidden until you hover over its area",
onChange: () => setSettings({
hideUntilHover: !settings().hideUntilHover
}),
get checked() {
return settings().hideUntilHover;
}
}), null);
insert(_el$2, createComponent(Checkbox, {
label: "Completely hide trigger",
description: "Completely removes the trigger from the DOM (you can still open it with the hotkey)",
onChange: () => setSettings({
triggerHidden: !settings().triggerHidden
}),
get checked() {
return settings().triggerHidden;
}
}), null);
insert(_el$2, createComponent(Select, {
label: "Theme",
options: [{
label: "Dark",
value: "dark"
}, {
label: "Light",
value: "light"
}],
get value() {
return settings().theme;
},
onChange: (theme) => setSettings({
theme
})
}), null);
effect(() => className(_el$2, styles().settingsGroup));
return _el$2;
})()];
}
}), createComponent(Section, {
get children() {
return [createComponent(SectionTitle, {
get children() {
return [createComponent(SectionIcon, {
get children() {
return createComponent(Link, {});
}
}), "URL Configuration"];
}
}), createComponent(SectionDescription, {
children: "Control when devtools are available based on URL parameters."
}), (() => {
var _el$3 = _tmpl$7();
insert(_el$3, createComponent(Checkbox, {
label: "Require URL Flag",
description: "Only show devtools when a specific URL parameter is present",
get checked() {
return settings().requireUrlFlag;
},
onChange: (checked) => setSettings({
requireUrlFlag: checked
})
}), null);
insert(_el$3, createComponent(Show, {
get when() {
return settings().requireUrlFlag;
},
get children() {
var _el$4 = _tmpl$7();
insert(_el$4, createComponent(Input, {
label: "URL flag",
description: "Enter the URL parameter name (for example, 'debug' for ?debug=true)",
placeholder: "debug",
get value() {
return settings().urlFlag;
},
onChange: (value) => setSettings({
urlFlag: value
})
}));
effect(() => className(_el$4, styles().conditionalSetting));
return _el$4;
}
}), null);
effect(() => className(_el$3, styles().settingsGroup));
return _el$3;
})()];
}
}), createComponent(Section, {
get children() {
return [createComponent(SectionTitle, {
get children() {
return [createComponent(SectionIcon, {
get children() {
return createComponent(Keyboard, {});
}
}), "Keyboard"];
}
}), createComponent(SectionDescription, {
children: "Customize keyboard shortcuts for quick access."
}), (() => {
var _el$5 = _tmpl$7();
insert(_el$5, createComponent(HotkeyConfig, {
title: "Open/Close Devtools",
description: "Hotkey to open/close devtools",
get hotkey() {
return settings().openHotkey;
},
modifiers,
onHotkeyChange: (hotkey) => setSettings({
openHotkey: hotkey
})
}), null);
insert(_el$5, createComponent(HotkeyConfig, {
title: "Source Inspector",
description: "Hotkey to open source inspector",
get hotkey() {
return settings().inspectHotkey;
},
modifiers,
onHotkeyChange: (hotkey) => setSettings({
inspectHotkey: hotkey
})
}), null);
effect(() => className(_el$5, styles().settingsStack));
return _el$5;
})()];
}
}), createComponent(Section, {
get children() {
return [createComponent(SectionTitle, {
get children() {
return [createComponent(SectionIcon, {
get children() {
return createComponent(GeoTag, {});
}
}), "Position"];
}
}), createComponent(SectionDescription, {
children: "Adjust the position of the trigger button and devtools panel."
}), (() => {
var _el$6 = _tmpl$25(), _el$7 = _el$6.firstChild;
insert(_el$6, createComponent(Select, {
label: "Trigger Mode",
description: "Fixed anchors the trigger to a corner. Floating lets you drag and throw it.",
get value() {
return settings().triggerMode;
},
options: [{
label: "Fixed",
value: "fixed"
}, {
label: "Floating",
value: "floating"
}],
onChange: (value) => setSettings({
triggerMode: value
})
}), _el$7);
insert(_el$7, createComponent(Show, {
get when() {
return settings().triggerMode === "fixed";
},
get children() {
return createComponent(Select, {
label: "Trigger Position",
options: [{
label: "Bottom Right",
value: "bottom-right"
}, {
label: "Bottom Left",
value: "bottom-left"
}, {
label: "Top Right",
value: "top-right"
}, {
label: "Top Left",
value: "top-left"
}, {
label: "Middle Right",
value: "middle-right"
}, {
label: "Middle Left",
value: "middle-left"
}],
get value() {
return settings().position;
},
onChange: (value) => setSettings({
position: value
})
});
}
}), null);
insert(_el$7, createComponent(Select, {
label: "Panel Position",
get value() {
return settings().panelLocation;
},
options: [{
label: "Top",
value: "top"
}, {
label: "Bottom",
value: "bottom"
}],
onChange: (value) => setSettings({
panelLocation: value
})
}), null);
effect((_p$) => {
var _v$ = styles().settingsGroup, _v$2 = styles().settingRow;
_v$ !== _p$.e && className(_el$6, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$7, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$6;
})()];
}
})];
}
}));
return _el$;
})();
};
// src/tabs/marketplace/card-utils.ts
var getButtonText = (card) => {
if (card.status === "installing") return "Installing...";
if (card.status === "success") return "Installed!";
if (card.status === "error") return "Error";
switch (card.actionType) {
case "install":
return "Install";
case "install-devtools":
return "Install Devtools";
case "add-to-devtools":
return "Add to Devtools";
case "requires-package":
return `Requires ${card.requiredPackageName}`;
case "wrong-framework":
return "Different Framework";
case "already-installed":
return "Already Installed";
case "bump-version":
return "Bump Version";
case "version-mismatch":
return "Version Mismatch";
default:
return "Install";
}
};
var getButtonVariant = (card) => {
if (card.actionType === "requires-package" || card.actionType === "wrong-framework" || card.actionType === "version-mismatch")
return "danger";
if (card.actionType === "bump-version") return "warning";
if (card.actionType === "already-installed") return "secondary";
return "primary";
};
var getBadgeClass = (card, styles) => {
const s = styles();
const base = s.pluginMarketplaceCardBadge;
switch (card.actionType) {
case "install":
case "install-devtools":
return `${base} ${s.pluginMarketplaceCardBadgeInstall}`;
case "add-to-devtools":
return `${base} ${s.pluginMarketplaceCardBadgeAdd}`;
// Active matches the green rule down the card's leading edge, so the two
// signals for "this one is running" agree instead of using two colours.
case "already-installed":
return `${base} ${s.pluginMarketplaceCardBadgeActive}`;
case "bump-version":
case "version-mismatch":
return `${base} ${s.pluginMarketplaceCardBadgeBlocked}`;
case "requires-package":
case "wrong-framework":
return `${base} ${s.pluginMarketplaceCardBadgeRequires}`;
default:
return base;
}
};
var getBadgeText = (card) => {
switch (card.actionType) {
case "install":
case "install-devtools":
return "Available";
case "add-to-devtools":
return "Installed";
case "already-installed":
return "Active";
case "version-mismatch":
return "Incompatible";
case "requires-package":
return "Unavailable";
case "wrong-framework":
return "Other Framework";
default:
return "";
}
};
// src/tabs/marketplace/plugin-card.tsx
var _tmpl$8 = /* @__PURE__ */ template(`<img>`);
var _tmpl$26 = /* @__PURE__ */ template(`<span>New`);
var _tmpl$34 = /* @__PURE__ */ template(`<span>v<!> \u2022 min v`);
var _tmpl$42 = /* @__PURE__ */ template(`<p>`);
var _tmpl$52 = /* @__PURE__ */ template(`<a target=_blank rel="noopener noreferrer">Documentation `);
var _tmpl$62 = /* @__PURE__ */ template(`<div>`);
var _tmpl$72 = /* @__PURE__ */ template(`<div data-tsd-surface style=position:relative><span></span><div></div><div><h3></h3><p></p><p>`);
var _tmpl$82 = /* @__PURE__ */ template(`<span>v<!> \u2022 requires v<!>+`);
var _tmpl$9 = /* @__PURE__ */ template(`<span>`);
var _tmpl$0 = /* @__PURE__ */ template(`<span>Installing...`);
var _tmpl$1 = /* @__PURE__ */ template(`<span>Installed!`);
var _tmpl$10 = /* @__PURE__ */ template(`<div role=status>`);
var PluginCardComponent = (props) => {
const styles = createStyles();
const {
card
} = props;
return (() => {
var _el$ = _tmpl$72(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$5 = _el$3.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.nextSibling, _el$9 = _el$8.nextSibling;
insert(_el$2, () => getBadgeText(card));
insert(_el$3, createComponent(Show, {
get when() {
return card.metadata?.logoUrl;
},
get fallback() {
return createComponent(PackageIcon$1, {});
},
get children() {
var _el$4 = _tmpl$8();
effect((_p$) => {
var _v$ = card.metadata?.logoUrl, _v$2 = card.metadata?.title || card.devtoolsPackage, _v$3 = styles().pluginMarketplaceCardImage;
_v$ !== _p$.e && setAttribute(_el$4, "src", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$4, "alt", _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$4, _p$.a = _v$3);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$4;
}
}));
insert(_el$6, () => card.metadata?.title || card.devtoolsPackage, null);
insert(_el$6, createComponent(Show, {
get when() {
return card.metadata?.isNew;
},
get children() {
var _el$7 = _tmpl$26();
effect(() => className(_el$7, styles().pluginMarketplaceNewBanner));
return _el$7;
}
}), null);
insert(_el$8, () => card.devtoolsPackage);
insert(_el$9, (() => {
var _c$ = memo(() => card.actionType === "requires-package");
return () => _c$() ? `Requires ${card.requiredPackageName}` : memo(() => card.actionType === "wrong-framework")() ? `For different framework projects` : memo(() => card.actionType === "already-installed")() ? `Active in your devtools` : memo(() => card.actionType === "version-mismatch")() ? card.versionInfo?.reason || "Version incompatible" : card.metadata?.description || `For ${card.requiredPackageName}`;
})());
insert(_el$5, createComponent(Show, {
get when() {
return card.versionInfo;
},
get children() {
var _el$0 = _tmpl$42();
insert(_el$0, createComponent(Show, {
get when() {
return card.versionInfo?.satisfied;
},
get fallback() {
return (() => {
var _el$16 = _tmpl$82(), _el$17 = _el$16.firstChild, _el$20 = _el$17.nextSibling, _el$18 = _el$20.nextSibling, _el$21 = _el$18.nextSibling; _el$21.nextSibling;
insert(_el$16, () => card.versionInfo?.current, _el$20);
insert(_el$16, () => card.versionInfo?.required, _el$21);
effect(() => className(_el$16, styles().pluginMarketplaceCardVersionUnsatisfied));
return _el$16;
})();
},
get children() {
var _el$1 = _tmpl$34(), _el$10 = _el$1.firstChild, _el$12 = _el$10.nextSibling; _el$12.nextSibling;
insert(_el$1, () => card.versionInfo?.current, _el$12);
insert(_el$1, () => card.versionInfo?.required, null);
effect(() => className(_el$1, styles().pluginMarketplaceCardVersionSatisfied));
return _el$1;
}
}));
effect(() => className(_el$0, styles().pluginMarketplaceCardVersionInfo));
return _el$0;
}
}), null);
insert(_el$5, createComponent(Show, {
get when() {
return card.metadata?.docsUrl;
},
get children() {
var _el$13 = _tmpl$52(); _el$13.firstChild;
insert(_el$13, createComponent(ExternalLinkIcon, {}), null);
effect((_p$) => {
var _v$4 = card.metadata?.docsUrl, _v$5 = styles().pluginMarketplaceCardDocsLink;
_v$4 !== _p$.e && setAttribute(_el$13, "href", _p$.e = _v$4);
_v$5 !== _p$.t && className(_el$13, _p$.t = _v$5);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$13;
}
}), null);
insert(_el$5, createComponent(Show, {
get when() {
return memo(() => !!card.metadata?.tags)() && card.metadata.tags.length > 0;
},
get children() {
var _el$15 = _tmpl$62();
insert(_el$15, createComponent(For, {
get each() {
return card.metadata?.tags;
},
children: (tag) => (() => {
var _el$22 = _tmpl$9();
insert(_el$22, tag);
effect(() => className(_el$22, styles().pluginMarketplaceCardTag));
return _el$22;
})()
}));
effect(() => className(_el$15, styles().pluginMarketplaceCardTags));
return _el$15;
}
}), null);
insert(_el$, createComponent(Show, {
get when() {
return card.status === "idle";
},
get fallback() {
return (() => {
var _el$23 = _tmpl$10();
insert(_el$23, createComponent(Show, {
get when() {
return card.status === "installing";
},
get children() {
return [(() => {
var _el$24 = _tmpl$62();
effect(() => className(_el$24, styles().pluginMarketplaceCardSpinner));
return _el$24;
})(), (() => {
var _el$25 = _tmpl$0();
effect(() => className(_el$25, styles().pluginMarketplaceCardStatusText));
return _el$25;
})()];
}
}), null);
insert(_el$23, createComponent(Show, {
get when() {
return card.status === "success";
},
get children() {
return [createComponent(CheckCircleIcon, {}), (() => {
var _el$26 = _tmpl$1();
effect(() => className(_el$26, styles().pluginMarketplaceCardStatusText));
return _el$26;
})()];
}
}), null);
insert(_el$23, createComponent(Show, {
get when() {
return card.status === "error";
},
get children() {
return [createComponent(XCircleIcon, {}), (() => {
var _el$27 = _tmpl$9();
insert(_el$27, () => card.error || "Failed to install");
effect(() => className(_el$27, styles().pluginMarketplaceCardStatusTextError));
return _el$27;
})()];
}
}), null);
effect(() => className(_el$23, styles().pluginMarketplaceCardStatus));
return _el$23;
})();
},
get children() {
return createComponent(Button, {
get variant() {
return getButtonVariant(card);
},
onClick: () => props.onAction(card),
get disabled() {
return card.status !== "idle" || card.actionType === "requires-package" || card.actionType === "wrong-framework" || card.actionType === "already-installed" || card.actionType === "version-mismatch";
},
get ["class"]() {
return memo(() => card.actionType === "already-installed")() ? styles().pluginMarketplaceButtonInstalled : "";
},
get children() {
return getButtonText(card);
}
});
}
}), null);
effect((_p$) => {
var _v$6 = styles().pluginMarketplaceCard, _v$7 = {
[styles().pluginMarketplaceCardDisabled]: !card.isCurrentFramework && card.actionType !== "already-installed",
[styles().pluginMarketplaceCardFeatured]: !!card.metadata?.featured && card.actionType !== "already-installed",
[styles().pluginMarketplaceCardActive]: card.actionType === "already-installed"
}, _v$8 = getBadgeClass(card, styles), _v$9 = styles().pluginMarketplaceCardIcon, _v$0 = !!card.metadata?.logoUrl, _v$1 = styles().pluginMarketplaceCardHeader, _v$10 = styles().pluginMarketplaceCardTitle, _v$11 = styles().pluginMarketplaceCardPackageBadge, _v$12 = styles().pluginMarketplaceCardDescriptionText;
_v$6 !== _p$.e && className(_el$, _p$.e = _v$6);
_p$.t = classList(_el$, _v$7, _p$.t);
_v$8 !== _p$.a && className(_el$2, _p$.a = _v$8);
_v$9 !== _p$.o && className(_el$3, _p$.o = _v$9);
_v$0 !== _p$.i && _el$3.classList.toggle("custom-logo", _p$.i = _v$0);
_v$1 !== _p$.n && className(_el$5, _p$.n = _v$1);
_v$10 !== _p$.s && className(_el$6, _p$.s = _v$10);
_v$11 !== _p$.h && className(_el$8, _p$.h = _v$11);
_v$12 !== _p$.r && className(_el$9, _p$.r = _v$12);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0
});
return _el$;
})();
};
// src/tabs/marketplace/plugin-section.tsx
var _tmpl$11 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=currentColor><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z">`);
var _tmpl$27 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=2 stroke-linecap=round stroke-linejoin=round><rect x=2 y=4 width=20 height=16 rx=2></rect><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7">`);
var _tmpl$35 = /* @__PURE__ */ template(`<div><div><h4><span></span>Want to be featured here?</h4><p>If you've built a plugin for TanStack Devtools and would like to showcase it in the featured section, we'd love to hear from you! Reach out to us to discuss partnership opportunities.</p><a href="mailto:partners+devtools@tanstack.com?subject=Featured%20Plugin%20Partnership%20Inquiry"><span></span>Contact Us`);
var _tmpl$43 = /* @__PURE__ */ template(`<div><div>`);
var _tmpl$53 = /* @__PURE__ */ template(`<div><div role=button tabindex=0 data-tsd-control><div><div></div><h3>`);
var StarIcon = () => _tmpl$11();
var MailIcon = () => _tmpl$27();
var PluginSectionComponent = (props) => {
const styles = createStyles();
return (() => {
var _el$3 = _tmpl$53(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling;
_el$4.$$keydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
props.onToggleCollapse();
}
};
addEventListener(_el$4, "click", props.onToggleCollapse, true);
insert(_el$6, createComponent(ChevronDownIcon, {}));
insert(_el$7, () => props.section.displayName);
insert(_el$3, createComponent(Show, {
get when() {
return !props.isCollapsed();
},
get children() {
var _el$8 = _tmpl$43(), _el$14 = _el$8.firstChild;
insert(_el$8, createComponent(Show, {
get when() {
return props.section.id === "featured";
},
get children() {
var _el$9 = _tmpl$35(), _el$0 = _el$9.firstChild, _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$1.nextSibling, _el$12 = _el$11.nextSibling, _el$13 = _el$12.firstChild;
insert(_el$10, createComponent(StarIcon, {}));
insert(_el$13, createComponent(MailIcon, {}));
effect((_p$) => {
var _v$ = styles().pluginMarketplaceFeatureBanner, _v$2 = styles().pluginMarketplaceFeatureBannerContent, _v$3 = styles().pluginMarketplaceFeatureBannerTitle, _v$4 = styles().pluginMarketplaceFeatureBannerIcon, _v$5 = styles().pluginMarketplaceFeatureBannerText, _v$6 = styles().pluginMarketplaceFeatureBannerButton, _v$7 = styles().pluginMarketplaceFeatureBannerButtonIcon;
_v$ !== _p$.e && className(_el$9, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$0, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$1, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$10, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$11, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$12, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$13, _p$.s = _v$7);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
});
return _el$9;
}
}), _el$14);
insert(_el$14, createComponent(For, {
get each() {
return props.section.cards;
},
children: (card) => createComponent(PluginCardComponent, {
card,
get onAction() {
return props.onCardAction;
}
})
}));
effect((_p$) => {
var _v$8 = styles().pluginMarketplaceSectionContent, _v$9 = styles().pluginMarketplaceGrid;
_v$8 !== _p$.e && className(_el$8, _p$.e = _v$8);
_v$9 !== _p$.t && className(_el$14, _p$.t = _v$9);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$8;
}
}), null);
effect((_p$) => {
var _v$0 = styles().pluginMarketplaceSection, _v$1 = !props.isCollapsed(), _v$10 = styles().pluginMarketplaceSectionHeader, _v$11 = styles().pluginMarketplaceSectionHeaderLeft, _v$12 = styles().pluginMarketplaceSectionChevron, _v$13 = {
[styles().pluginMarketplaceSectionChevronCollapsed]: props.isCollapsed()
}, _v$14 = styles().pluginMarketplaceSectionTitle;
_v$0 !== _p$.e && className(_el$3, _p$.e = _v$0);
_v$1 !== _p$.t && setAttribute(_el$4, "aria-expanded", _p$.t = _v$1);
_v$10 !== _p$.a && className(_el$4, _p$.a = _v$10);
_v$11 !== _p$.o && className(_el$5, _p$.o = _v$11);
_v$12 !== _p$.i && className(_el$6, _p$.i = _v$12);
_p$.n = classList(_el$6, _v$13, _p$.n);
_v$14 !== _p$.s && className(_el$7, _p$.s = _v$14);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
});
return _el$3;
})();
};
delegateEvents(["click", "keydown"]);
var _tmpl$12 = /* @__PURE__ */ template(`<div data-tsd-surface role=dialog aria-label="Marketplace settings"><div><h3>Marketplace Settings</h3><button type=button aria-label="Close marketplace settings"data-tsd-control></button></div><div>`);
var SettingsPanel = (props) => {
const styles = createStyles();
return createComponent(Show, {
get when() {
return props.isOpen();
},
get children() {
var _el$ = _tmpl$12(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$2.nextSibling;
addEventListener(_el$4, "click", props.onClose, true);
insert(_el$4, createComponent(CloseIcon, {}));
insert(_el$5, createComponent(Checkbox, {
label: "Show active plugins",
description: "Display installed plugins in a separate section",
get checked() {
return props.showActivePlugins();
},
onChange: (checked) => props.setShowActivePlugins(checked)
}));
effect((_p$) => {
var _v$ = styles().pluginMarketplaceSettingsPanel, _v$2 = styles().pluginMarketplaceSettingsPanelHeader, _v$3 = styles().pluginMarketplaceSettingsPanelTitle, _v$4 = styles().pluginMarketplaceSettingsPanelClose, _v$5 = styles().pluginMarketplaceSettingsPanelContent;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0
});
return _el$;
}
});
};
delegateEvents(["click"]);
var _tmpl$13 = /* @__PURE__ */ template(`<div>`);
var _tmpl$28 = /* @__PURE__ */ template(`<button type=button data-tsd-control>`);
var TagFilters = (props) => {
const styles = createStyles();
return createComponent(Show, {
get when() {
return props.tags().length > 0;
},
get children() {
var _el$ = _tmpl$13();
insert(_el$, createComponent(For, {
get each() {
return props.tags();
},
children: (tag) => (() => {
var _el$2 = _tmpl$28();
_el$2.$$click = () => props.onToggleTag(tag);
insert(_el$2, tag);
effect((_p$) => {
var _v$ = props.selectedTags().has(tag), _v$2 = props.selectedTags().has(tag), _v$3 = styles().pluginMarketplaceTagButton, _v$4 = {
[styles().pluginMarketplaceTagButtonActive]: props.selectedTags().has(tag)
};
_v$ !== _p$.e && setAttribute(_el$2, "data-tsd-selected", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$2, "aria-pressed", _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$2, _p$.a = _v$3);
_p$.o = classList(_el$2, _v$4, _p$.o);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0
});
return _el$2;
})()
}));
effect(() => className(_el$, styles().pluginMarketplaceTagsContainer));
return _el$;
}
});
};
delegateEvents(["click"]);
// src/tabs/marketplace/marketplace-header.tsx
var _tmpl$14 = /* @__PURE__ */ template(`<div><div><div><h2>Plugin Marketplace</h2><p>Discover and install devtools for TanStack Query, Router, Form, and Pacer</p></div><div data-testid=marketplace-controls><div><input type=text aria-label="Search plugins"data-tsd-control placeholder="Search plugins..."></div><button type=button aria-label="Marketplace settings"data-tsd-control>`);
var MarketplaceHeader = (props) => {
const styles = createStyles();
return (() => {
var _el$ = _tmpl$14(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$6 = _el$3.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$7.firstChild, _el$9 = _el$7.nextSibling;
insert(_el$7, createComponent(SearchIcon, {}), _el$8);
_el$8.$$input = (e) => props.onSearchInput(e.currentTarget.value);
addEventListener(_el$9, "click", props.onSettingsClick, true);
insert(_el$9, createComponent(SettingsIcon, {}));
insert(_el$, createComponent(TagFilters, {
get tags() {
return props.tags;
},
get selectedTags() {
return props.selectedTags;
},
get onToggleTag() {
return props.onToggleTag;
}
}), null);
effect((_p$) => {
var _v$ = styles().pluginMarketplaceHeader, _v$2 = styles().pluginMarketplaceTitleRow, _v$3 = styles().pluginMarketplaceTitleBlock, _v$4 = styles().pluginMarketplaceTitle, _v$5 = styles().pluginMarketplaceDescription, _v$6 = styles().pluginMarketplaceControls, _v$7 = styles().pluginMarketplaceSearchWrapper, _v$8 = styles().pluginMarketplaceSearch, _v$9 = styles().pluginMarketplaceSettingsButton;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$6, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$7, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$8, _p$.h = _v$8);
_v$9 !== _p$.r && className(_el$9, _p$.r = _v$9);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0
});
effect(() => _el$8.value = props.searchInput());
return _el$;
})();
};
delegateEvents(["input", "click"]);
// src/tabs/marketplace/types.ts
var FRAMEWORKS = [
"react",
"solid",
"vue",
"svelte",
"angular"
];
// src/tabs/plugin-registry.ts
var PLUGIN_REGISTRY = {
// TanStack Query
"@tanstack/react-query-devtools": {
packageName: "@tanstack/react-query-devtools",
title: "TanStack Query Devtools",
description: "Powerful devtools for TanStack Query - inspect queries, mutations, and cache",
requires: {
packageName: "@tanstack/react-query",
minVersion: "5.0.0"
},
pluginId: "tanstack-query",
docsUrl: "https://tanstack.com/query/latest/docs/devtools",
author: "TanStack",
framework: "react",
featured: true,
// Featured plugin
tags: ["TanStack", "data-fetching", "caching", "state-management"]
},
"@tanstack/solid-query-devtools": {
packageName: "@tanstack/solid-query-devtools",
title: "TanStack Query Devtools",
description: "Powerful devtools for TanStack Query - inspect queries, mutations, and cache",
requires: {
packageName: "@tanstack/solid-query",
minVersion: "5.0.0"
},
pluginId: "tanstack-query",
docsUrl: "https://tanstack.com/query/latest/docs/devtools",
author: "TanStack",
framework: "solid",
tags: ["TanStack", "data-fetching", "caching", "state-management"]
},
// TanStack Router
"@tanstack/react-router-devtools": {
packageName: "@tanstack/react-router-devtools",
title: "TanStack Router Devtools",
description: "Inspect routes, navigation, and router state in real-time",
requires: {
packageName: "@tanstack/react-router",
minVersion: "1.0.0"
},
pluginId: "tanstack-router",
docsUrl: "https://tanstack.com/router/latest/docs/devtools",
author: "TanStack",
framework: "react",
featured: true,
// Featured plugin
tags: ["TanStack", "routing", "navigation"]
},
"@tanstack/solid-router-devtools": {
packageName: "@tanstack/solid-router-devtools",
title: "TanStack Router Devtools",
description: "Inspect routes, navigation, and router state in real-time",
requires: {
packageName: "@tanstack/solid-router",
minVersion: "1.0.0"
},
pluginId: "tanstack-router",
docsUrl: "https://tanstack.com/router/latest/docs/devtools",
author: "TanStack",
framework: "solid",
tags: ["TanStack", "routing", "navigation"]
},
// TanStack Form
"@tanstack/react-form-devtools": {
packageName: "@tanstack/react-form-devtools",
title: "TanStack Form Devtools",
description: "Debug form state, validation, and field values",
requires: {
packageName: "@tanstack/react-form",
minVersion: "1.23.0"
},
pluginImport: {
importName: "FormDevtoolsPlugin",
type: "function"
},
pluginId: "tanstack-form",
docsUrl: "https://tanstack.com/form/latest/docs/devtools",
author: "TanStack",
framework: "react",
isNew: true,
tags: ["TanStack", "forms", "validation"]
},
"@tanstack/solid-form-devtools": {
packageName: "@tanstack/solid-form-devtools",
title: "TanStack Form Devtools",
description: "Debug form state, validation, and field values",
requires: {
packageName: "@tanstack/solid-form",
minVersion: "1.23.0"
},
pluginImport: {
importName: "FormDevtoolsPlugin",
type: "function"
},
pluginId: "tanstack-form",
docsUrl: "https://tanstack.com/form/latest/docs/devtools",
author: "TanStack",
isNew: true,
framework: "solid",
tags: ["TanStack", "forms", "validation"]
},
// TanStack Pacer (Example - adjust as needed)
"@tanstack/react-pacer-devtools": {
packageName: "@tanstack/react-pacer-devtools",
title: "Pacer Devtools",
description: "Monitor and debug your Pacer animations and transitions",
requires: {
packageName: "@tanstack/react-pacer",
minVersion: "0.16.4"
},
author: "TanStack",
framework: "react",
isNew: true,
// New plugin banner
tags: ["TanStack"]
},
"@tanstack/solid-pacer-devtools": {
packageName: "@tanstack/solid-pacer-devtools",
title: "Pacer Devtools",
description: "Monitor and debug your Pacer animations and transitions",
requires: {
packageName: "@tanstack/solid-pacer",
minVersion: "0.14.4"
},
author: "TanStack",
framework: "solid",
isNew: true,
// New plugin banner
tags: ["TanStack"]
},
// TanStack A11y Devtools
"@tanstack/devtools-a11y": {
packageName: "@tanstack/devtools-a11y",
title: "Accessibility Devtools",
description: "Audit accessibility issues in real-time with axe-core. Supports WCAG 2.1/2.2, live monitoring, and visual overlays.",
pluginImport: {
importName: "createA11yPlugin",
type: "function"
},
pluginId: "devtools-a11y",
docsUrl: "https://tanstack.com/devtools/latest/docs/plugins/a11y",
author: "TanStack",
framework: "react",
isNew: true,
tags: ["TanStack", "a11y"]
},
// TanStack AI
"@tanstack/react-ai-devtools": {
packageName: "@tanstack/react-ai-devtools",
title: "TanStack AI Devtools",
description: "Debug TanStack AI - inspect messages, token usage, streaming chunks, tool calls, and reasoning.",
requires: {
packageName: "@tanstack/ai-react",
minVersion: "0.8.0"
},
pluginImport: {
importName: "aiDevtoolsPlugin",
type: "function"
},
pluginId: "tanstack-ai",
docsUrl: "https://tanstack.com/ai",
repoUrl: "https://github.com/TanStack/ai",
author: "TanStack",
framework: "react",
isNew: true,
tags: ["TanStack", "AI", "streaming"]
},
// ==========================================
// THIRD-PARTY PLUGINS - Examples
// ==========================================
// External contributors can add their plugins below!
// Dimano — Prefetch Heatmap for TanStack Router
"@dimano/ts-devtools-plugin-prefetch-heatmap": {
packageName: "@dimano/ts-devtools-plugin-prefetch-heatmap",
title: "Prefetch Heatmap",
description: "Visualize TanStack Router prefetch intent, hits, and waste with a color overlay and a live metrics panel.",
requires: {
packageName: "@tanstack/react-router",
minVersion: "1.0.0"
},
// default export registers the plugin
pluginImport: {
importName: "registerPrefetchHeatmapPlugin",
type: "function"
},
// helps the host match your plugin deterministically
pluginId: "prefetch-heatmap",
// show a nice card in the marketplace
logoUrl: "https://raw.githubusercontent.com/dimitrianoudi/tanstack-prefetch-heatmap/main/assets/prefetch-heatmap-card.png",
docsUrl: "https://github.com/dimitrianoudi/tanstack-prefetch-heatmap#prefetch-heatmap-devtools-plugin",
repoUrl: "https://github.com/dimitrianoudi/tanstack-prefetch-heatmap",
author: "Dimitris Anoudis (@dimitrianoudi)",
framework: "react",
isNew: true,
tags: ["Router", "Prefetch", "Analytics", "Overlay", "TanStack"]
},
// Santos Vilanculos — Inertia 3 Devtools for React
"@santosvilanculos/bevor-react": {
packageName: "@santosvilanculos/bevor-react",
title: "Inertia 3 Devtools",
description: "Inertia 3 devtools built on top of TanStack DevTools",
pluginImport: {
importName: "inertiaDevtoolsPlugin",
type: "function"
},
pluginId: "inertia-devtools",
logoUrl: "https://raw.githubusercontent.com/santosvilanculos/bevor/main/logo.png",
docsUrl: "https://github.com/SantosVilanculos/bevor/tree/main/packages/react",
repoUrl: "https://github.com/SantosVilanculos/bevor",
author: "Santos Vilanculos (santosvilanculos@yahoo.com)",
framework: "react",
isNew: true,
tags: ["TanStack", "React", "Inertia", "Laravel"]
}
};
function getAllPluginMetadata() {
return Object.values(PLUGIN_REGISTRY);
}
// src/tabs/semver-utils.ts
function parseVersion(version) {
if (!version) return null;
const cleanVersion = version.replace(/^[v^~]/, "").split("-")[0]?.split("+")[0];
if (!cleanVersion) return null;
const parts = cleanVersion.split(".");
if (parts.length < 2) return null;
const major = parseInt(parts[0] ?? "0", 10);
const minor = parseInt(parts[1] ?? "0", 10);
const patch = parseInt(parts[2] ?? "0", 10);
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
return null;
}
return {
major,
minor,
patch,
raw: version
};
}
function compareVersions(v1, v2) {
if (v1.major !== v2.major) return v1.major - v2.major;
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
return v1.patch - v2.patch;
}
function satisfiesMinVersion(currentVersion, minVersion) {
const current = parseVersion(currentVersion);
const min = parseVersion(minVersion);
if (!current || !min) return true;
return compareVersions(current, min) >= 0;
}
function satisfiesMaxVersion(currentVersion, maxVersion) {
const current = parseVersion(currentVersion);
const max = parseVersion(maxVersion);
if (!current || !max) return true;
return compareVersions(current, max) <= 0;
}
function satisfiesVersionRange(currentVersion, minVersion, maxVersion) {
if (!minVersion && !maxVersion) {
return { satisfied: true };
}
if (minVersion && !satisfiesMinVersion(currentVersion, minVersion)) {
return {
satisfied: false,
reason: `Requires v${minVersion} or higher (current: v${currentVersion})`
};
}
if (maxVersion && !satisfiesMaxVersion(currentVersion, maxVersion)) {
return {
satisfied: false,
reason: `Requires v${maxVersion} or lower (current: v${currentVersion})`
};
}
return { satisfied: true };
}
// src/tabs/marketplace/plugin-utils.ts
var detectFramework = (pkg, frameworks) => {
const allDeps = {
...pkg.dependencies,
...pkg.devDependencies
};
const frameworkPackageMap = {
react: ["react", "react-dom"],
vue: ["vue", "@vue/core"],
solid: ["solid-js"],
svelte: ["svelte"],
angular: ["@angular/core"]
};
for (const framework of frameworks) {
const frameworkPackages = frameworkPackageMap[framework];
if (frameworkPackages && frameworkPackages.some((pkg2) => allDeps[pkg2])) {
return framework;
}
}
return "unknown";
};
var isPluginRegistered = (registeredPlugins, packageName, pluginName, framework, pluginId) => {
if (pluginId) {
return Array.from(registeredPlugins).some((id) => {
const idLower = id.toLowerCase();
const pluginIdLower = pluginId.toLowerCase();
return idLower.startsWith(pluginIdLower) || idLower.includes(pluginIdLower);
});
}
if (registeredPlugins.has(packageName)) return true;
const pluginWords = pluginName.toLowerCase().split(/[-_/@]/).filter((word) => word.length > 0);
const frameworkPart = framework.toLowerCase();
return Array.from(registeredPlugins).some((id) => {
const idLower = id.toLowerCase();
if (idLower.includes(pluginName.toLowerCase())) {
return true;
}
const matchedWords = pluginWords.filter((word) => idLower.includes(word));
if (matchedWords.length >= 2) {
return true;
}
if (idLower.includes(frameworkPart) && matchedWords.length >= 1) {
return true;
}
return false;
});
};
var buildPluginCards = (pkg, currentFramework, registeredPlugins, existingCards) => {
const allDeps = {
...pkg.dependencies,
...pkg.devDependencies
};
const allCards = [];
const allPlugins = getAllPluginMetadata();
allPlugins.forEach((metadata) => {
const devtoolsPackage = metadata.packageName;
const isCurrentFramework = metadata.framework === currentFramework || metadata.framework === "other";
const requiredPackageName = metadata.requires?.packageName;
const hasPackage = requiredPackageName ? !!allDeps[requiredPackageName] : false;
const hasDevtools = !!allDeps[devtoolsPackage];
let versionInfo;
if (hasPackage && metadata.requires) {
const currentVersion = requiredPackageName ? allDeps[requiredPackageName] : void 0;
if (currentVersion) {
const versionCheck = satisfiesVersionRange(
currentVersion,
metadata.requires.minVersion,
metadata.requires.maxVersion
);
versionInfo = {
current: currentVersion,
required: metadata.requires.minVersion,
satisfied: versionCheck.satisfied,
reason: versionCheck.reason
};
}
}
const isRegistered = isPluginRegistered(
registeredPlugins,
devtoolsPackage,
metadata.packageName,
metadata.framework,
metadata.pluginId
);
let actionType;
if (!isCurrentFramework) {
actionType = "wrong-framework";
} else if (metadata.requires && !hasPackage) {
actionType = "requires-package";
} else if (versionInfo && !versionInfo.satisfied) {
actionType = "bump-version";
} else if (hasDevtools && isRegistered) {
actionType = "already-installed";
} else if (hasDevtools && !isRegistered) {
actionType = "add-to-devtools";
} else if (!hasDevtools && metadata.requires && hasPackage) {
actionType = "install-devtools";
} else if (!hasDevtools) {
actionType = "install";
} else {
actionType = "install";
}
const existing = existingCards.find(
(c) => c.devtoolsPackage === devtoolsPackage
);
allCards.push({
requiredPackageName: requiredPackageName || "",
devtoolsPackage,
framework: metadata.framework,
hasPackage,
hasDevtools,
isRegistered,
actionType,
status: existing?.status || "idle",
error: existing?.error,
isCurrentFramework,
metadata,
versionInfo
});
});
return allCards;
};
var groupIntoSections = (allCards) => {
const sections = [];
const featuredCards = allCards.filter(
(c) => c.metadata?.featured && c.actionType !== "already-installed" && c.isCurrentFramework
// Only show featured plugins for current framework
);
sections.push({
id: "featured",
displayName: "Featured",
cards: featuredCards
});
const activeCards = allCards.filter(
(c) => c.actionType === "already-installed" && c.isRegistered
);
if (activeCards.length > 0) {
sections.push({
id: "active",
displayName: "Active Plugins",
cards: activeCards
});
}
const availableCards = allCards.filter(
(c) => c.isCurrentFramework && c.actionType !== "already-installed" && !c.metadata?.featured
// Not featured (already in featured section)
);
if (availableCards.length > 0) {
sections.push({
id: "available",
displayName: "Available Plugins",
cards: availableCards
});
}
return sections;
};
// src/tabs/plugin-marketplace.tsx
var _tmpl$15 = /* @__PURE__ */ template(`<div><p>`);
var _tmpl$29 = /* @__PURE__ */ template(`<div data-testid=plugin-marketplace data-tsd-surface><div>`);
var PluginMarketplace = () => {
const styles = createStyles();
const {
plugins
} = createPlugins();
const [pluginSections, setPluginSections] = createSignal([]);
const [currentPackageJson, setCurrentPackageJson] = createSignal(null);
const [searchInput, setSearchInput] = createSignal("");
const [searchQuery, setSearchQuery] = createSignal("");
const [collapsedSections, setCollapsedSections] = createSignal(/* @__PURE__ */ new Set());
const [showActivePlugins, setShowActivePlugins] = createSignal(true);
const [selectedTags, setSelectedTags] = createSignal(/* @__PURE__ */ new Set());
const [isSettingsOpen, setIsSettingsOpen] = createSignal(false);
let debounceTimeout;
const handleSearchInput = (value) => {
setSearchInput(value);
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(() => {
setSearchQuery(value);
}, 300);
};
const toggleSection = (framework) => {
setCollapsedSections((prev) => {
const newSet = new Set(prev);
if (newSet.has(framework)) {
newSet.delete(framework);
} else {
newSet.add(framework);
}
return newSet;
});
};
const matchesSearch = (card, query) => {
if (!query) return true;
const lowerQuery = query.toLowerCase();
return card.devtoolsPackage.toLowerCase().includes(lowerQuery) || card.requiredPackageName.toLowerCase().includes(lowerQuery) || card.framework.toLowerCase().includes(lowerQuery);
};
const getFilteredSections = () => {
const query = searchQuery();
const showActive = showActivePlugins();
const tags = selectedTags();
const pkg = currentPackageJson();
if (!pkg) return [];
const currentFramework = detectFramework(pkg, FRAMEWORKS);
const registeredPlugins = new Set(plugins()?.map((p) => p.id || "") || []);
const allCards = buildPluginCards(
pkg,
currentFramework,
registeredPlugins,
pluginSections().flatMap((s) => s.cards)
// Preserve status from existing cards
);
let sections = groupIntoSections(allCards);
if (!showActive) {
sections = sections.filter((section) => section.id !== "active");
}
if (tags.size > 0) {
sections = sections.map((section) => ({
...section,
cards: section.cards.filter((card) => {
if (!card.metadata?.tags) return false;
return card.metadata.tags.some((tag) => tags.has(tag));
})
})).filter((section) => section.cards.length > 0);
}
if (!query) return sections;
return sections.map((section) => ({
...section,
cards: section.cards.filter((card) => matchesSearch(card, query))
})).filter((section) => section.cards.length > 0);
};
onMount(() => {
const cleanupJsonRead = devtoolsEventClient.on("package-json-read", (event) => {
setCurrentPackageJson(event.payload.packageJson);
updatePluginCards(event.payload.packageJson);
});
const cleanupJsonUpdated = devtoolsEventClient.on("package-json-updated", (event) => {
setCurrentPackageJson(event.payload.packageJson);
updatePluginCards(event.payload.packageJson);
});
const cleanupDevtoolsInstalled = devtoolsEventClient.on("devtools-installed", (event) => {
setPluginSections((prevSections) => prevSections.map((section) => ({
...section,
cards: section.cards.map((card) => card.devtoolsPackage === event.payload.packageName ? {
...card,
status: event.payload.success ? "success" : "error",
error: event.payload.error
} : card)
})));
});
const cleanupPluginAdded = devtoolsEventClient.on("plugin-added", (event) => {
setPluginSections((prevSections) => prevSections.map((section) => ({
...section,
cards: section.cards.map((card) => card.devtoolsPackage === event.payload.packageName ? {
...card,
status: event.payload.success ? "success" : "error",
error: event.payload.error
} : card)
})));
if (event.payload.success) {
const pkg = currentPackageJson();
if (pkg) {
updatePluginCards(pkg);
}
}
});
const requestPackageJson = () => devtoolsEventClient.emit("mounted", void 0);
let refetchAttempts = 0;
const refetchInterval = setInterval(() => {
if (currentPackageJson() || refetchAttempts >= 10) {
clearInterval(refetchInterval);
return;
}
refetchAttempts++;
requestPackageJson();
}, 400);
onCleanup(() => {
cleanupJsonRead();
cleanupJsonUpdated();
cleanupDevtoolsInstalled();
cleanupPluginAdded();
clearInterval(refetchInterval);
});
requestPackageJson();
});
const updatePluginCards = (pkg) => {
if (!pkg) return;
const currentFramework = detectFramework(pkg, FRAMEWORKS);
const registeredPlugins = new Set(plugins()?.map((p) => p.id || "") || []);
const allCards = buildPluginCards(pkg, currentFramework, registeredPlugins, pluginSections().flatMap((s) => s.cards));
const sections = groupIntoSections(allCards);
setPluginSections(sections);
};
const handleAction = (card) => {
if (card.actionType === "requires-package" || card.actionType === "wrong-framework" || card.actionType === "already-installed" || card.actionType === "version-mismatch") {
return;
}
setPluginSections((prevSections) => prevSections.map((section) => ({
...section,
cards: section.cards.map((c) => c.devtoolsPackage === card.devtoolsPackage ? {
...c,
status: "installing"
} : c)
})));
if (card.actionType === "bump-version") {
devtoolsEventClient.emit("bump-package-version", {
packageName: card.requiredPackageName,
devtoolsPackage: card.devtoolsPackage,
pluginName: card.metadata?.title || card.devtoolsPackage,
minVersion: card.metadata?.requires?.minVersion,
pluginImport: card.metadata?.pluginImport
});
return;
}
if (card.actionType === "add-to-devtools") {
devtoolsEventClient.emit("add-plugin-to-devtools", {
packageName: card.devtoolsPackage,
// should always be defined
pluginName: card.metadata?.title ?? card.devtoolsPackage,
pluginImport: card.metadata?.pluginImport
});
return;
}
devtoolsEventClient.emit("install-devtools", {
packageName: card.devtoolsPackage,
// should always be defined
pluginName: card.metadata?.title ?? card.devtoolsPackage,
pluginImport: card.metadata?.pluginImport
});
};
const getAllTags = () => {
const tags = /* @__PURE__ */ new Set();
pluginSections().forEach((section) => {
if (section.id === "featured" || section.id === "available") {
section.cards.forEach((card) => {
if (card.metadata?.tags) {
card.metadata.tags.forEach((tag) => tags.add(tag));
}
});
}
});
return Array.from(tags).sort();
};
const toggleTag = (tag) => {
setSelectedTags((prev) => {
const newTags = new Set(prev);
if (newTags.has(tag)) {
newTags.delete(tag);
} else {
newTags.add(tag);
}
return newTags;
});
};
return (() => {
var _el$ = _tmpl$29(), _el$2 = _el$.firstChild;
insert(_el$2, createComponent(MarketplaceHeader, {
searchInput,
onSearchInput: handleSearchInput,
onSettingsClick: () => setIsSettingsOpen(!isSettingsOpen()),
tags: getAllTags,
selectedTags,
onToggleTag: toggleTag
}), null);
insert(_el$2, createComponent(Show, {
get when() {
return getFilteredSections().length > 0;
},
get children() {
return createComponent(For, {
get each() {
return getFilteredSections();
},
children: (section) => createComponent(PluginSectionComponent, {
section,
isCollapsed: () => collapsedSections().has(section.id),
onToggleCollapse: () => toggleSection(section.id),
onCardAction: handleAction
})
});
}
}), null);
insert(_el$2, createComponent(Show, {
get when() {
return getFilteredSections().length === 0;
},
get children() {
var _el$3 = _tmpl$15(), _el$4 = _el$3.firstChild;
insert(_el$4, (() => {
var _c$ = memo(() => !!searchQuery());
return () => _c$() ? `No plugins found matching "${searchQuery()}"` : "No additional plugins available. You have all compatible devtools installed and registered!";
})());
effect((_p$) => {
var _v$ = styles().pluginMarketplaceEmpty, _v$2 = styles().pluginMarketplaceEmptyText;
_v$ !== _p$.e && className(_el$3, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$4, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$3;
}
}), null);
insert(_el$, createComponent(SettingsPanel, {
isOpen: isSettingsOpen,
onClose: () => setIsSettingsOpen(false),
showActivePlugins,
setShowActivePlugins
}), null);
effect((_p$) => {
var _v$3 = styles().pluginMarketplace, _v$4 = styles().pluginMarketplaceScroll;
_v$3 !== _p$.e && className(_el$, _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$2, _p$.t = _v$4);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
})();
};
// src/tabs/plugins-tab.tsx
var _tmpl$16 = /* @__PURE__ */ template(`<div data-tsd-surface>`);
var PluginsTab = () => {
const {
plugins
} = createPlugins();
const styles = createStyles();
return createComponent(Show, {
get when() {
return (plugins()?.length ?? 0) === 0;
},
get children() {
var _el$ = _tmpl$16();
insert(_el$, createComponent(PluginMarketplace, {}));
effect(() => className(_el$, styles().pluginsTabContent));
return _el$;
}
});
};
var isStyleNode = (node) => {
const element = node.nodeType === 3 ? node.parentNode : node;
return element?.nodeName === "STYLE";
};
function createHeadChanges(onChange, opts = {}) {
const {
attributes = true,
childList = true,
subtree = true,
observeTitle = true
} = opts;
onMount(() => {
const headObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.type === "childList") {
m.addedNodes.forEach((node) => {
if (!isStyleNode(node)) onChange({ kind: "added", node }, m);
});
m.removedNodes.forEach((node) => {
if (!isStyleNode(node)) onChange({ kind: "removed", node }, m);
});
} else if (m.type === "attributes") {
if (isStyleNode(m.target)) continue;
const el = m.target;
onChange(
{
kind: "attr",
target: el,
name: m.attributeName,
oldValue: m.oldValue ?? null
},
m
);
} else {
const isInTitle = m.target.parentNode && m.target.parentNode.tagName.toLowerCase() === "title";
if (isInTitle) onChange({ kind: "title", title: document.title }, m);
}
}
});
headObserver.observe(document.head, {
childList,
attributes,
subtree,
attributeOldValue: attributes,
characterData: true,
// helps catch <title> text node edits
characterDataOldValue: false
});
let titleObserver;
if (observeTitle) {
const titleEl = document.head.querySelector("title") || // create a <title> if missing so future changes are observable
document.head.appendChild(document.createElement("title"));
titleObserver = new MutationObserver(() => {
onChange({ kind: "title", title: document.title });
});
titleObserver.observe(titleEl, {
childList: true,
characterData: true,
subtree: true
});
}
onCleanup(() => {
headObserver.disconnect();
titleObserver?.disconnect();
});
});
}
// src/tabs/seo-tab/social-previews.tsx
var _tmpl$17 = /* @__PURE__ */ template(`<div><div data-testid=social-preview-heading><span aria-hidden=true></span> Preview</div><div data-testid=social-preview-title></div><div></div><div>`);
var _tmpl$210 = /* @__PURE__ */ template(`<img alt=Preview>`);
var _tmpl$36 = /* @__PURE__ */ template(`<div>No image`);
var _tmpl$44 = /* @__PURE__ */ template(`<div>`);
var _tmpl$54 = /* @__PURE__ */ template(`<div><strong>Missing tags for <!>:</strong><ul>`);
var _tmpl$63 = /* @__PURE__ */ template(`<li>`);
var SOCIALS = [
{
network: "Facebook",
tags: [{
key: "og:title",
prop: "title"
}, {
key: "og:description",
prop: "description"
}, {
key: "og:image",
prop: "image"
}, {
key: "og:url",
prop: "url"
}],
color: "#4267B2"
// semantic-color-exempt: vendor-mark
},
{
network: "X/Twitter",
tags: [{
key: "twitter:title",
prop: "title"
}, {
key: "twitter:description",
prop: "description"
}, {
key: "twitter:image",
prop: "image"
}, {
key: "twitter:url",
prop: "url"
}],
color: "#1DA1F2"
// semantic-color-exempt: vendor-mark
},
{
network: "LinkedIn",
tags: [{
key: "og:title",
prop: "title"
}, {
key: "og:description",
prop: "description"
}, {
key: "og:image",
prop: "image"
}, {
key: "og:url",
prop: "url"
}],
color: "#0077B5"
// semantic-color-exempt: vendor-mark
},
{
network: "Discord",
tags: [{
key: "og:title",
prop: "title"
}, {
key: "og:description",
prop: "description"
}, {
key: "og:image",
prop: "image"
}, {
key: "og:url",
prop: "url"
}],
color: "#5865F2"
// semantic-color-exempt: vendor-mark
},
{
network: "Slack",
tags: [{
key: "og:title",
prop: "title"
}, {
key: "og:description",
prop: "description"
}, {
key: "og:image",
prop: "image"
}, {
key: "og:url",
prop: "url"
}],
color: "#4A154B"
// semantic-color-exempt: vendor-mark
},
{
network: "Mastodon",
tags: [{
key: "og:title",
prop: "title"
}, {
key: "og:description",
prop: "description"
}, {
key: "og:image",
prop: "image"
}, {
key: "og:url",
prop: "url"
}],
color: "#6364FF"
// semantic-color-exempt: vendor-mark
},
{
network: "Bluesky",
tags: [{
key: "og:title",
prop: "title"
}, {
key: "og:description",
prop: "description"
}, {
key: "og:image",
prop: "image"
}, {
key: "og:url",
prop: "url"
}],
color: "#1185FE"
// semantic-color-exempt: vendor-mark
}
// Add more networks as needed
];
function SocialPreview(props) {
const styles = createStyles();
return (() => {
var _el$ = _tmpl$17(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$2.nextSibling, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling;
insert(_el$2, () => props.network, _el$4);
insert(_el$, (() => {
var _c$ = memo(() => !!props.meta.image);
return () => _c$() ? (() => {
var _el$8 = _tmpl$210();
effect((_p$) => {
var _v$8 = props.meta.image, _v$9 = styles().seoPreviewImage;
_v$8 !== _p$.e && setAttribute(_el$8, "src", _p$.e = _v$8);
_v$9 !== _p$.t && className(_el$8, _p$.t = _v$9);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$8;
})() : (() => {
var _el$9 = _tmpl$36();
effect(() => className(_el$9, `${styles().seoPreviewImage} ${styles().seoPreviewImagePlaceholder}`));
return _el$9;
})();
})(), _el$5);
insert(_el$5, () => props.meta.title || "No Title");
insert(_el$6, () => props.meta.description || "No Description");
insert(_el$7, () => props.meta.url || window.location.href);
effect((_p$) => {
var _v$ = styles().seoPreviewCard, _v$2 = styles().seoPreviewHeader, _v$3 = styles().seoPreviewNetworkDot, _v$4 = props.color, _v$5 = styles().seoPreviewTitle, _v$6 = styles().seoPreviewDesc, _v$7 = styles().seoPreviewUrl;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
_v$4 !== _p$.o && setStyleProperty(_el$3, "background", _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$6, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$7, _p$.s = _v$7);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
});
return _el$;
})();
}
function SocialPreviewsSection() {
const [reports, setReports] = createSignal(analyzeHead());
const styles = createStyles();
function analyzeHead() {
const metaTags = Array.from(document.head.querySelectorAll("meta"));
const reports2 = [];
for (const social of SOCIALS) {
const found = {};
const missing = [];
for (const tag of social.tags) {
const meta = metaTags.find((m) => (tag.key.includes("twitter:") ? false : m.getAttribute("property") === tag.key) || m.getAttribute("name") === tag.key);
if (meta && meta.getAttribute("content")) {
found[tag.prop] = meta.getAttribute("content") || void 0;
} else {
missing.push(tag.key);
}
}
reports2.push({
network: social.network,
found,
missing
});
}
return reports2;
}
createHeadChanges(() => {
setReports(analyzeHead());
});
return createComponent(Section, {
get children() {
return [createComponent(SectionDescription, {
children: "See how your current page will look when shared on popular social networks. The tool checks for essential meta tags and highlights any that are missing."
}), (() => {
var _el$0 = _tmpl$44();
insert(_el$0, createComponent(For, {
get each() {
return reports();
},
children: (report, i) => {
const social = SOCIALS[i()];
return (() => {
var _el$1 = _tmpl$44();
insert(_el$1, createComponent(SocialPreview, {
get meta() {
return report.found;
},
get color() {
return social.color;
},
get network() {
return social.network;
}
}), null);
insert(_el$1, (() => {
var _c$2 = memo(() => report.missing.length > 0);
return () => _c$2() ? (() => {
var _el$10 = _tmpl$54(), _el$11 = _el$10.firstChild, _el$12 = _el$11.firstChild, _el$14 = _el$12.nextSibling; _el$14.nextSibling; var _el$15 = _el$11.nextSibling;
insert(_el$11, () => social?.network, _el$14);
insert(_el$15, createComponent(For, {
get each() {
return report.missing;
},
children: (tag) => (() => {
var _el$16 = _tmpl$63();
insert(_el$16, tag);
effect(() => className(_el$16, styles().seoMissingTag));
return _el$16;
})()
}));
effect((_p$) => {
var _v$0 = styles().seoMissingTagsSection, _v$1 = styles().seoMissingTagsList;
_v$0 !== _p$.e && className(_el$10, _p$.e = _v$0);
_v$1 !== _p$.t && className(_el$15, _p$.t = _v$1);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$10;
})() : null;
})(), null);
return _el$1;
})();
}
}));
effect(() => className(_el$0, styles().seoPreviewSection));
return _el$0;
})()];
}
});
}
var _tmpl$18 = /* @__PURE__ */ template(`<div><div data-testid=serp-preview-label></div><div><div><div><span data-testid=serp-preview-site-name></span><span></span></div></div><div data-testid=serp-preview-title>`);
var _tmpl$211 = /* @__PURE__ */ template(`<img alt="favicon icon">`);
var _tmpl$37 = /* @__PURE__ */ template(`<div>`);
var _tmpl$45 = /* @__PURE__ */ template(`<div><strong>Issues for <!>:</strong><ul>`);
var _tmpl$55 = /* @__PURE__ */ template(`<li>`);
var TITLE_MAX_CHARS = 60;
var DESCRIPTION_MAX_CHARS = 158;
var DESCRIPTION_MOBILE_MAX_CHARS = 120;
var ELLIPSIS = "...";
var COMMON_CHECKS = [{
message: "No favicon or icon set on the page.",
hasIssue: (data) => !data.favicon
}, {
message: "No title tag set on the page.",
hasIssue: (data) => !data.title.trim()
}, {
message: "No meta description set on the page.",
hasIssue: (data) => !data.description.trim()
}, {
message: "The title is wider than 600px and it may not be displayed in full length.",
hasIssue: (_, overflow) => overflow.titleOverflow
}];
var SERP_PREVIEWS = [{
label: "Desktop preview",
isMobile: false,
extraChecks: [{
message: "The meta description may get trimmed at ~960 pixels on desktop and at ~680px on mobile. Keep it below ~158 characters.",
hasIssue: (_, overflow) => overflow.descriptionOverflow
}]
}, {
label: "Mobile preview",
isMobile: true,
extraChecks: [{
message: "Description exceeds the 3-line limit for mobile view. Please shorten your text to fit within 3 lines.",
hasIssue: (_, overflow) => overflow.descriptionOverflowMobile
}]
}];
function truncateToChars(text, maxChars) {
if (text.length <= maxChars) return text;
if (maxChars <= ELLIPSIS.length) return ELLIPSIS;
return text.slice(0, maxChars - ELLIPSIS.length) + ELLIPSIS;
}
function getSerpFromHead() {
const title = document.title || "";
const url = typeof window !== "undefined" ? window.location.href : "";
const metaTags = Array.from(document.head.querySelectorAll("meta"));
const descriptionMeta = metaTags.find((m) => m.getAttribute("name")?.toLowerCase() === "description");
const description = descriptionMeta?.getAttribute("content")?.trim() || "";
const siteNameMeta = metaTags.find((m) => m.getAttribute("property") === "og:site_name");
const siteName = siteNameMeta?.getAttribute("content")?.trim() || (typeof window !== "undefined" ? window.location.hostname.replace(/^www\./, "") : "");
const linkTags = Array.from(document.head.querySelectorAll("link"));
const iconLink = linkTags.find((l) => l.getAttribute("rel")?.toLowerCase().split(/\s+/).includes("icon"));
let favicon = iconLink?.getAttribute("href") || null;
if (favicon && typeof window !== "undefined") {
try {
favicon = new URL(favicon, url).href;
} catch {
favicon = null;
}
}
return {
title,
description,
siteName,
favicon,
url
};
}
function getSerpIssues(data, overflow, checks) {
return checks.filter((c) => c.hasIssue(data, overflow)).map((c) => c.message);
}
function SerpSnippetPreview(props) {
const styles = createStyles();
return (() => {
var _el$ = _tmpl$18(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$4.nextSibling;
insert(_el$2, () => props.label);
insert(_el$4, (() => {
var _c$ = memo(() => !!props.data.favicon);
return () => _c$() ? (() => {
var _el$9 = _tmpl$211();
effect((_p$) => {
var _v$9 = props.data.favicon, _v$0 = styles().serpSnippetFavicon;
_v$9 !== _p$.e && setAttribute(_el$9, "src", _p$.e = _v$9);
_v$0 !== _p$.t && className(_el$9, _p$.t = _v$0);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$9;
})() : (() => {
var _el$0 = _tmpl$37();
effect(() => className(_el$0, styles().serpSnippetDefaultFavicon));
return _el$0;
})();
})(), _el$5);
insert(_el$6, () => props.data.siteName || props.data.url);
insert(_el$7, () => props.data.url);
insert(_el$8, () => props.displayTitle || props.data.title || "No title");
insert(_el$3, (() => {
var _c$2 = memo(() => !!!props.isMobile);
return () => _c$2() && (() => {
var _el$1 = _tmpl$37();
insert(_el$1, () => props.displayDescription || props.data.description || "No meta description.");
effect(() => className(_el$1, styles().serpSnippetDesc));
return _el$1;
})();
})(), null);
insert(_el$3, (() => {
var _c$3 = memo(() => !!props.isMobile);
return () => _c$3() && (() => {
var _el$10 = _tmpl$37();
insert(_el$10, () => props.displayDescription || props.data.description || "No meta description.");
effect(() => className(_el$10, styles().serpSnippetDescMobile));
return _el$10;
})();
})(), null);
insert(_el$, (() => {
var _c$4 = memo(() => props.issues.length > 0);
return () => _c$4() ? (() => {
var _el$11 = _tmpl$45(), _el$12 = _el$11.firstChild, _el$13 = _el$12.firstChild, _el$15 = _el$13.nextSibling; _el$15.nextSibling; var _el$16 = _el$12.nextSibling;
insert(_el$12, () => props.label, _el$15);
insert(_el$16, createComponent(For, {
get each() {
return props.issues;
},
children: (issue) => (() => {
var _el$17 = _tmpl$55();
insert(_el$17, issue);
effect(() => className(_el$17, styles().serpReportItem));
return _el$17;
})()
}));
effect((_p$) => {
var _v$1 = styles().seoMissingTagsSection, _v$10 = styles().serpErrorList;
_v$1 !== _p$.e && className(_el$11, _p$.e = _v$1);
_v$10 !== _p$.t && className(_el$16, _p$.t = _v$10);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$11;
})() : null;
})(), null);
effect((_p$) => {
var _v$ = styles().serpPreviewBlock, _v$2 = styles().serpPreviewLabel, _v$3 = props.isMobile ? styles().serpSnippetMobile : styles().serpSnippet, _v$4 = styles().serpSnippetTopRow, _v$5 = styles().serpSnippetSiteColumn, _v$6 = styles().serpSnippetSiteName, _v$7 = styles().serpSnippetSiteUrl, _v$8 = styles().serpSnippetTitle;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$6, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$7, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$8, _p$.h = _v$8);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0
});
return _el$;
})();
}
function SerpPreviewSection() {
const [serp, setSerp] = createSignal(getSerpFromHead());
createHeadChanges(() => {
setSerp(getSerpFromHead());
});
const serpPreviewState = createMemo(() => {
const data = serp();
const titleText = data.title || "No title";
const descText = data.description || "No meta description.";
const displayTitle = truncateToChars(titleText, TITLE_MAX_CHARS);
const displayDescription = truncateToChars(descText, DESCRIPTION_MAX_CHARS);
return {
displayTitle,
displayDescription,
overflow: {
titleOverflow: titleText.length > TITLE_MAX_CHARS,
descriptionOverflow: descText.length > DESCRIPTION_MAX_CHARS,
descriptionOverflowMobile: descText.length > DESCRIPTION_MOBILE_MAX_CHARS
}
};
});
return createComponent(Section, {
get children() {
return [createComponent(SectionDescription, {
children: "See how your title tag and meta description may look in Google search results. Data is read from the current page."
}), createComponent(For, {
each: SERP_PREVIEWS,
children: (preview) => {
const issues = createMemo(() => getSerpIssues(serp(), serpPreviewState().overflow, [...COMMON_CHECKS, ...preview.extraChecks]));
return createComponent(SerpSnippetPreview, {
get data() {
return serp();
},
get displayTitle() {
return serpPreviewState().displayTitle;
},
get displayDescription() {
return serpPreviewState().displayDescription;
},
get isMobile() {
return preview.isMobile;
},
get label() {
return preview.label;
},
get issues() {
return issues();
}
});
}
})];
}
});
}
// src/tabs/seo-tab/index.tsx
var _tmpl$19 = /* @__PURE__ */ template(`<div data-testid=seo-workspace data-tsd-surface>`);
var SeoTab = () => {
const [activeView, setActiveView] = createSignal("social-previews");
const styles = createStyles();
return (() => {
var _el$ = _tmpl$19();
insert(_el$, createComponent(WorkbenchSecondaryTabs, {
ariaLabel: "SEO sections",
get children() {
return [createComponent(WorkbenchSecondaryTab, {
get selected() {
return activeView() === "social-previews";
},
get ariaCurrent() {
return activeView() === "social-previews" ? "page" : void 0;
},
onClick: () => setActiveView("social-previews"),
children: "Social previews"
}), createComponent(WorkbenchSecondaryTab, {
get selected() {
return activeView() === "serp-preview";
},
get ariaCurrent() {
return activeView() === "serp-preview" ? "page" : void 0;
},
onClick: () => setActiveView("serp-preview"),
children: "SERP preview"
})];
}
}), null);
insert(_el$, createComponent(MainPanel$1, {
withPadding: true,
get ["class"]() {
return styles().seoContent;
},
get children() {
return [createComponent(Show, {
get when() {
return activeView() === "social-previews";
},
get children() {
return createComponent(SocialPreviewsSection, {});
}
}), createComponent(Show, {
get when() {
return activeView() === "serp-preview";
},
get children() {
return createComponent(SerpPreviewSection, {});
}
})];
}
}), null);
effect(() => className(_el$, styles().seoWorkspace));
return _el$;
})();
};
// src/tabs/index.tsx
var tabs = [{
name: "Plugins",
id: "plugins",
component: () => createComponent(PluginsTab, {})
}, {
name: "SEO",
id: "seo",
component: () => createComponent(SeoTab, {})
}, {
name: "Settings",
id: "settings",
component: () => createComponent(SettingsTab, {})
}];
var _tmpl$20 = /* @__PURE__ */ template(`<div role=group aria-label="Panes in this group"style=position:absolute>`);
var _tmpl$212 = /* @__PURE__ */ template(`<span><span><button type=button data-tsd-group-tab data-tsd-control></button></span><button type=button data-tsd-control>`);
var _tmpl$38 = /* @__PURE__ */ template(`<div data-testid=plugins-empty-state data-tsd-surface style=position:absolute;inset:0><span aria-hidden=true></span><p>No plugin open</p><p>Pick a plugin from the strip above to open its panel. You can keep up to <!> open, split side by side or stacked as tabs.`);
var _tmpl$46 = /* @__PURE__ */ template(`<div data-testid=plugins-workspace data-tsd-surface><p aria-live=polite data-testid=plugin-workspace-status></p><p id=tsd-pane-move-hint>Press Enter to pick this pane up and move it with the arrow keys.`);
var _tmpl$56 = /* @__PURE__ */ template(`<div data-plugin-mount data-tsd-surface style=position:absolute>`);
var _tmpl$64 = /* @__PURE__ */ template(`<div role=separator tabindex=0 data-tsd-control data-tsd-separator=plugin-pane data-testid=plugin-splitter aria-label="Resize plugin panes"aria-valuemin=0 aria-valuemax=100>`);
var _tmpl$73 = /* @__PURE__ */ template(`<div data-testid=plugin-drop-overlay>`);
var _tmpl$83 = /* @__PURE__ */ template(`<span aria-hidden=true data-testid=plugin-drag-preview>`);
var insetRect = (rect, inset) => ({
left: rect.left + inset,
top: rect.top + inset,
width: Math.max(rect.width - inset * 2, 0),
height: Math.max(rect.height - inset * 2, 0)
});
var expandSplitterRect = (handle, extra) => 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
};
var KEYBOARD_STEP = 0.02;
var KEYBOARD_STEP_COARSE = 0.1;
var DRAG_HOLD_MS = 500;
var GroupTabBar = (props) => {
const styles = createStyles();
const sortable = createSortable({
get items() {
return props.tabs;
},
group: "tsd-plugin-panes",
axis: "x",
onReorder: (next) => props.onReorder(next),
onTransfer: ({
item,
to
}) => props.onTransfer(item, to)
});
return (() => {
var _el$ = _tmpl$20();
var _ref$ = sortable.ref;
typeof _ref$ === "function" ? use(_ref$, _el$) : sortable.ref = _el$;
insert(_el$, createComponent(For, {
get each() {
return props.tabs;
},
children: (tabId, index) => (() => {
var _el$2 = _tmpl$212(), _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$3.nextSibling;
spread(_el$3, mergeProps(() => sortable.row(tabId), {
get ["class"]() {
return styles().pluginGroupTabRow;
}
}), false, true);
_el$4.$$click = () => props.onSelect(tabId);
_el$4.$$keydown = (event) => props.onKeyDown(tabId, event);
_el$4.$$pointerdown = (event) => props.onPointerDown(tabId, event);
setAttribute(_el$4, "data-testid", `plugin-tab-${tabId}`);
insert(_el$4, () => props.titleOf(tabId));
_el$5.$$click = () => props.onClose(tabId);
_el$5.$$pointerup = () => props.onClose(tabId);
setAttribute(_el$5, "data-testid", `plugin-tab-close-${tabId}`);
insert(_el$5, createComponent(X, {
"aria-hidden": "true"
}));
effect((_p$) => {
var _v$7 = styles().pluginGroupTabItem, _v$8 = index() === props.activeIndex ? "true" : void 0, _v$9 = props.heldTabId === tabId ? "true" : void 0, _v$0 = index() === props.activeIndex, _v$1 = props.moveHintId, _v$10 = styles().pluginGroupTab, _v$11 = `Close ${props.titleOf(tabId)}`, _v$12 = styles().pluginGroupTabClose;
_v$7 !== _p$.e && className(_el$2, _p$.e = _v$7);
_v$8 !== _p$.t && setAttribute(_el$2, "data-tsd-selected", _p$.t = _v$8);
_v$9 !== _p$.a && setAttribute(_el$2, "data-tsd-held", _p$.a = _v$9);
_v$0 !== _p$.o && setAttribute(_el$4, "aria-pressed", _p$.o = _v$0);
_v$1 !== _p$.i && setAttribute(_el$4, "aria-describedby", _p$.i = _v$1);
_v$10 !== _p$.n && className(_el$4, _p$.n = _v$10);
_v$11 !== _p$.s && setAttribute(_el$5, "aria-label", _p$.s = _v$11);
_v$12 !== _p$.h && className(_el$5, _p$.h = _v$12);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0
});
return _el$2;
})()
}));
effect((_p$) => {
var _v$ = props.groupId, _v$2 = `plugin-group-tabs-${props.groupId}`, _v$3 = styles().pluginGroupTabs, _v$4 = `${props.rect?.left ?? 0}px`, _v$5 = `${props.rect?.top ?? 0}px`, _v$6 = `${props.rect?.width ?? 0}px`;
_v$ !== _p$.e && setAttribute(_el$, "data-tsd-group-tabs", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$, "data-testid", _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$, _p$.a = _v$3);
_v$4 !== _p$.o && setStyleProperty(_el$, "left", _p$.o = _v$4);
_v$5 !== _p$.i && setStyleProperty(_el$, "top", _p$.i = _v$5);
_v$6 !== _p$.n && setStyleProperty(_el$, "width", _p$.n = _v$6);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$;
})();
};
var PluginWorkspace = (props) => {
const {
plugins,
activePlugins,
layout,
setLayout
} = createPlugins();
const {
acceptStripDrags
} = createStripDrag();
const {
theme
} = createTheme();
const styles = createStyles();
const [pluginRefs, setPluginRefs] = createSignal(/* @__PURE__ */ new Map());
const [box, setBox] = createSignal({
w: 0,
h: 0
});
const [held, setHeld] = createSignal(null);
const [dropTarget, setDropTarget] = createSignal(null);
const [announcement, setAnnouncement] = createSignal("");
const [previewAt, setPreviewAt] = createSignal(null);
let workspaceEl;
const moveHintId = "tsd-pane-move-hint";
const measure = () => {
if (!workspaceEl) return;
const rect = workspaceEl.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) return;
setBox({
w: rect.width,
h: rect.height
});
};
const registerWorkspace = (el) => {
workspaceEl = el;
measure();
const Observer = globalThis.ResizeObserver;
if (!Observer) return;
const observer = new Observer(measure);
observer.observe(el);
onCleanup(() => observer.disconnect());
};
createEffect(() => {
props.visible;
props.isOpen;
measure();
});
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,
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(), paddedBox(), PLUGIN_SPLITTER_SIZE).map((handle) => ({
...handle,
rect: shift(handle.rect)
})));
const groups = createMemo(() => allGroups(layout()));
const paneOrder = createMemo(() => [...activePlugins()].sort(), [], {
equals: (a, b) => a.length === b.length && a.every((id, i) => id === b[i])
});
const cardRect = (groupId) => groupRects()[groupId] ?? null;
const tabBarRect = (groupId) => {
const card = cardRect(groupId);
if (!card) return null;
return {
...card,
height: PLUGIN_GROUP_TAB_HEIGHT
};
};
const paneRect = (groupId) => {
const card = cardRect(groupId);
if (!card) return null;
return {
left: card.left,
top: card.top + PLUGIN_GROUP_TAB_HEIGHT,
width: card.width,
height: Math.max(card.height - PLUGIN_GROUP_TAB_HEIGHT, 0)
};
};
const groupOf = (tabId) => findGroupOfTab(layout(), tabId);
const isVisibleTab = (tabId) => {
const group = groupOf(tabId);
return group !== null && group.tabs[group.active] === tabId;
};
const pluginById = (id) => plugins()?.find((entry) => entry.id === id);
const titleOf = (id) => {
const plugin = pluginById(id);
if (plugin === void 0) return id;
return typeof plugin.name === "string" ? plugin.name : id;
};
createEffect(() => {
for (const pluginId of activePlugins()) {
const plugin = pluginById(pluginId);
const ref = pluginRefs().get(pluginId);
if (plugin && ref) {
plugin.render(ref, {
theme: theme(),
devtoolsOpen: props.isOpen
});
}
}
});
const clearDrag = () => {
setHeld(null);
setDropTarget(null);
setPreviewAt(null);
};
createEffect(() => {
const host = workspaceEl?.closest(`#${TANSTACK_DEVTOOLS}`);
if (!host) return;
const className = styles().pluginDraggingCursor;
if (previewAt() === null) {
host.classList.remove(className);
return;
}
host.classList.add(className);
onCleanup(() => host.classList.remove(className));
});
const announce = (message) => setAnnouncement(message);
const resolveTarget = (groupId, point) => {
const rect = groupRects()[groupId];
if (!rect) return null;
if (point.y <= rect.top + PLUGIN_GROUP_TAB_HEIGHT) {
return {
groupId,
zone: "center",
willStack: true
};
}
const zone = zoneAt(point, paneRect(groupId) ?? rect, PANE_DROP_EDGE_RATIO);
const willStack = zone === "center" || !canSplit(layout(), groupId, zone, MIN_PANE_SIZE, paddedBox(), PLUGIN_SPLITTER_SIZE);
return {
groupId,
zone,
willStack
};
};
const commitDrop = (target, tabId) => {
if (target === null) return;
if (target.groupId === null) {
setLayout(singleGroup([tabId]));
return;
}
const next = target.willStack ? stackInto(layout(), target.groupId, tabId) : splitAt(layout(), target.groupId, target.zone, tabId);
setLayout(next);
};
const toLocal = (point) => {
const origin = workspaceEl?.getBoundingClientRect();
return {
x: point.x - (origin?.left ?? 0),
y: point.y - (origin?.top ?? 0)
};
};
const localPoint = (event) => toLocal({
x: event.clientX,
y: event.clientY
});
const startTabDrag = (tabId, event) => {
if (event.button !== 0) return;
measure();
let dragging = false;
let latest = {
x: event.clientX,
y: event.clientY
};
const holdTimer = window.setTimeout(() => {
dragging = true;
setHeld({
tabId
});
setPreviewAt(latest);
setDropTarget(targetAt(toLocal(latest)));
}, DRAG_HOLD_MS);
const move = (moveEvent) => {
latest = {
x: moveEvent.clientX,
y: moveEvent.clientY
};
if (!dragging) return;
setPreviewAt(latest);
setDropTarget(targetAt(localPoint(moveEvent)));
};
const up = () => {
window.clearTimeout(holdTimer);
document.removeEventListener("pointermove", move);
document.removeEventListener("pointerup", up);
if (dragging) commitDrop(dropTarget(), tabId);
clearDrag();
};
document.addEventListener("pointermove", move);
document.addEventListener("pointerup", up);
};
const groupAt = (point) => groups().find((entry) => {
const rect = groupRects()[entry.id];
return rect !== void 0 && point.x >= rect.left && point.x <= rect.left + rect.width && point.y >= rect.top && point.y <= rect.top + rect.height;
});
const insideWorkspace = (point) => point.x >= 0 && point.y >= 0 && point.x <= box().w && point.y <= box().h;
const targetAt = (point) => {
if (layout() === null) {
return insideWorkspace(point) ? {
groupId: null,
zone: "center",
willStack: true
} : null;
}
const over = groupAt(point);
return over ? resolveTarget(over.id, point) : null;
};
acceptStripDrags((pluginId, startPoint) => {
const alreadyOpen = activePlugins().includes(pluginId);
if (!alreadyOpen && activePlugins().length >= MAX_ACTIVE_PLUGINS) {
announce(`Already at the limit of ${MAX_ACTIVE_PLUGINS} open plugins`);
return;
}
measure();
setHeld({
tabId: pluginId
});
setPreviewAt(startPoint);
const aim = (point) => setDropTarget(targetAt(point));
aim(toLocal(startPoint));
const move = (event) => {
setPreviewAt({
x: event.clientX,
y: event.clientY
});
aim(localPoint(event));
};
const up = () => {
document.removeEventListener("pointermove", move);
document.removeEventListener("pointerup", up);
const target = dropTarget();
if (target === null) {
if (!alreadyOpen) announce(`${titleOf(pluginId)} was not placed`);
} else {
commitDrop(target, pluginId);
announce(`${titleOf(pluginId)} ${target.willStack ? "stacked as a tab" : `split to the ${target.zone}`}`);
}
clearDrag();
};
document.addEventListener("pointermove", move);
document.addEventListener("pointerup", up);
});
const startSplitterDrag = (handle, event) => {
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 = handle.extent > 0 ? (handle.dir === "row" ? MIN_PANE_SIZE.w : MIN_PANE_SIZE.h) / handle.extent : 0;
const previousUserSelect = document.body.style.userSelect;
document.body.style.userSelect = "none";
const move = (moveEvent) => {
moveEvent.preventDefault();
const now = handle.dir === "row" ? moveEvent.clientX : moveEvent.clientY;
setLayout(resizeFromPointer(original, handle.path, handle.gutterIndex, now - start, handle.extent, minFraction));
};
const up = (upEvent) => {
if (target instanceof HTMLElement && target.hasPointerCapture(upEvent.pointerId)) {
target.releasePointerCapture(upEvent.pointerId);
}
document.body.style.userSelect = previousUserSelect;
document.removeEventListener("pointermove", move);
document.removeEventListener("pointerup", up);
};
document.addEventListener("pointermove", move);
document.addEventListener("pointerup", up);
};
const resizeFromKeyboard = (handle, event) => {
const step = event.shiftKey ? KEYBOARD_STEP_COARSE : KEYBOARD_STEP;
const grows = handle.dir === "row" ? event.key === "ArrowRight" : event.key === "ArrowDown";
const shrinks = handle.dir === "row" ? event.key === "ArrowLeft" : event.key === "ArrowUp";
let delta;
if (grows) delta = step;
if (shrinks) delta = -step;
if (event.key === "Home") delta = -1;
if (event.key === "End") delta = 1;
if (delta === void 0) return;
event.preventDefault();
const minFraction = handle.extent > 0 ? (handle.dir === "row" ? MIN_PANE_SIZE.w : MIN_PANE_SIZE.h) / handle.extent : 0;
setLayout(resize(layout(), handle.path, handle.gutterIndex, delta, minFraction));
};
const moveModeKeys = (tabId, event) => {
const holding = held()?.tabId === tabId;
if (event.key === "Escape" && holding) {
event.preventDefault();
clearDrag();
announce(`${titleOf(tabId)} left where it was`);
return;
}
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
if (!holding) {
setHeld({
tabId
});
setDropTarget(null);
announce(`${titleOf(tabId)} picked up. Use the arrow keys to choose a place, Enter to drop, Escape to cancel.`);
return;
}
const target = dropTarget();
if (target === null) {
clearDrag();
announce(`${titleOf(tabId)} left where it was`);
} else {
commitDrop(target, tabId);
clearDrag();
announce(`${titleOf(tabId)} ${target.willStack ? "stacked as a tab" : `split to the ${target.zone}`}`);
}
return;
}
if (!holding) return;
const zoneForKey = {
ArrowLeft: "left",
ArrowRight: "right",
ArrowUp: "top",
ArrowDown: "bottom"
};
const zone = zoneForKey[event.key];
if (zone === void 0) return;
event.preventDefault();
const group = groupOf(tabId);
if (group === null) return;
const rect = groupRects()[group.id];
const neighbour = groups().find((entry) => {
if (entry.id === group.id) return false;
const other = groupRects()[entry.id];
if (!other || !rect) return false;
return zone === "left" ? other.left + other.width <= rect.left + 1 : zone === "right" ? other.left >= rect.left + rect.width - 1 : zone === "top" ? other.top + other.height <= rect.top + 1 : other.top >= rect.top + rect.height - 1;
});
setDropTarget(neighbour ? {
groupId: neighbour.id,
zone: "center",
willStack: true
} : resolveTarget(group.id, {
x: (rect?.left ?? 0) + (zone === "left" ? 1 : zone === "right" ? (rect?.width ?? 0) - 1 : (rect?.width ?? 0) / 2),
y: (rect?.top ?? 0) + (zone === "top" ? 1 : zone === "bottom" ? (rect?.height ?? 0) - 1 : (rect?.height ?? 0) / 2)
}));
};
const overlayRect = createMemo(() => {
const target = dropTarget();
if (target === null) return null;
if (target.groupId === null) {
return insetRect({
left: 0,
top: 0,
width: box().w,
height: box().h
}, PANE_CARD_INSET);
}
const rect = cardRect(target.groupId);
if (!rect) return null;
if (target.willStack) return rect;
const half = (value) => value / 2;
return target.zone === "left" ? {
...rect,
width: half(rect.width)
} : target.zone === "right" ? {
left: rect.left + half(rect.width),
top: rect.top,
width: half(rect.width),
height: rect.height
} : target.zone === "top" ? {
...rect,
height: half(rect.height)
} : {
left: rect.left,
top: rect.top + half(rect.height),
width: rect.width,
height: half(rect.height)
};
});
return (() => {
var _el$6 = _tmpl$46(), _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling;
use(registerWorkspace, _el$6);
insert(_el$6, createComponent(Show, {
get when() {
return activePlugins().length > 0;
},
get children() {
return [createComponent(For, {
get each() {
return groups();
},
children: (group) => createComponent(GroupTabBar, {
get groupId() {
return group.id;
},
get tabs() {
return group.tabs;
},
get activeIndex() {
return group.active;
},
get rect() {
return tabBarRect(group.id);
},
get heldTabId() {
return held()?.tabId ?? null;
},
titleOf,
moveHintId,
onReorder: (tabIds) => setLayout(setTabs(layout(), group.id, tabIds)),
onTransfer: (tabId, index) => setLayout(moveTab(layout(), tabId, group.id, index)),
onSelect: (tabId) => setLayout(activateTab(layout(), tabId)),
onClose: (tabId) => setLayout(closeTab(layout(), tabId)),
onPointerDown: startTabDrag,
onKeyDown: moveModeKeys
})
}), createComponent(For, {
get each() {
return paneOrder();
},
children: (pluginId) => {
onCleanup(() => {
pluginById(pluginId)?.destroy?.(pluginId);
setPluginRefs((previous) => {
const next = new Map(previous);
next.delete(pluginId);
return next;
});
});
const rect = () => paneRect(groupOf(pluginId)?.id ?? "");
return (() => {
var _el$14 = _tmpl$56();
use((el) => {
setPluginRefs((previous) => {
const next = new Map(previous);
next.set(pluginId, el);
return next;
});
}, _el$14);
setAttribute(_el$14, "id", `${PLUGIN_CONTAINER_ID}-${pluginId}`);
setAttribute(_el$14, "data-testid", `plugin-pane-${pluginId}`);
effect((_p$) => {
var _v$22 = styles().pluginsTabContent, _v$23 = `${rect()?.left ?? 0}px`, _v$24 = `${rect()?.top ?? 0}px`, _v$25 = `${rect()?.width ?? 0}px`, _v$26 = `${rect()?.height ?? 0}px`, _v$27 = isVisibleTab(pluginId) ? "block" : "none";
_v$22 !== _p$.e && className(_el$14, _p$.e = _v$22);
_v$23 !== _p$.t && setStyleProperty(_el$14, "left", _p$.t = _v$23);
_v$24 !== _p$.a && setStyleProperty(_el$14, "top", _p$.a = _v$24);
_v$25 !== _p$.o && setStyleProperty(_el$14, "width", _p$.o = _v$25);
_v$26 !== _p$.i && setStyleProperty(_el$14, "height", _p$.i = _v$26);
_v$27 !== _p$.n && setStyleProperty(_el$14, "display", _p$.n = _v$27);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$14;
})();
}
}), createComponent(Index, {
get each() {
return handles();
},
children: (handle) => {
const rect = () => expandSplitterRect(handle(), PANE_CARD_INSET);
return (() => {
var _el$15 = _tmpl$64();
_el$15.$$keydown = (event) => resizeFromKeyboard(handle(), event);
_el$15.$$pointerdown = (event) => startSplitterDrag(handle(), event);
effect((_p$) => {
var _v$28 = handle().dir === "row" ? "vertical" : "horizontal", _v$29 = Math.round((handle().dir === "row" ? handle().rect.left : handle().rect.top) / Math.max(handle().extent, 1) * 100), _v$30 = styles().pluginSplitter(handle().dir), _v$31 = `${rect().left}px`, _v$32 = `${rect().top}px`, _v$33 = `${rect().width}px`, _v$34 = `${rect().height}px`;
_v$28 !== _p$.e && setAttribute(_el$15, "aria-orientation", _p$.e = _v$28);
_v$29 !== _p$.t && setAttribute(_el$15, "aria-valuenow", _p$.t = _v$29);
_v$30 !== _p$.a && className(_el$15, _p$.a = _v$30);
_v$31 !== _p$.o && setStyleProperty(_el$15, "left", _p$.o = _v$31);
_v$32 !== _p$.i && setStyleProperty(_el$15, "top", _p$.i = _v$32);
_v$33 !== _p$.n && setStyleProperty(_el$15, "width", _p$.n = _v$33);
_v$34 !== _p$.s && setStyleProperty(_el$15, "height", _p$.s = _v$34);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
});
return _el$15;
})();
}
})];
}
}), _el$7);
insert(_el$6, createComponent(Show, {
get when() {
return overlayRect();
},
children: (rect) => (() => {
var _el$16 = _tmpl$73();
effect((_p$) => {
var _v$35 = dropTarget()?.willStack ? "stack" : "split", _v$36 = styles().pluginDropOverlay, _v$37 = `${rect().left}px`, _v$38 = `${rect().top}px`, _v$39 = `${rect().width}px`, _v$40 = `${rect().height}px`;
_v$35 !== _p$.e && setAttribute(_el$16, "data-tsd-drop-intent", _p$.e = _v$35);
_v$36 !== _p$.t && className(_el$16, _p$.t = _v$36);
_v$37 !== _p$.a && setStyleProperty(_el$16, "left", _p$.a = _v$37);
_v$38 !== _p$.o && setStyleProperty(_el$16, "top", _p$.o = _v$38);
_v$39 !== _p$.i && setStyleProperty(_el$16, "width", _p$.i = _v$39);
_v$40 !== _p$.n && setStyleProperty(_el$16, "height", _p$.n = _v$40);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$16;
})()
}), _el$7);
insert(_el$6, createComponent(Show, {
get when() {
return previewAt();
},
children: (at) => (
// Portalled to the body on purpose. `MainPanel` sets a `transform`,
// which makes it a containing block, so a `position: fixed` child would
// resolve against the panel instead of the viewport and get clipped by
// the workspace's `overflow: hidden`.
createComponent(Portal, {
get mount() {
return document.body;
},
get children() {
var _el$17 = _tmpl$83();
insert(_el$17, () => titleOf(held()?.tabId ?? ""));
effect((_p$) => {
var _v$41 = styles().pluginDragPreview, _v$42 = `${at().x}px`, _v$43 = `${at().y}px`;
_v$41 !== _p$.e && className(_el$17, _p$.e = _v$41);
_v$42 !== _p$.t && setStyleProperty(_el$17, "left", _p$.t = _v$42);
_v$43 !== _p$.a && setStyleProperty(_el$17, "top", _p$.a = _v$43);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$17;
}
})
)
}), _el$7);
insert(_el$7, announcement);
insert(_el$6, createComponent(Show, {
get when() {
return activePlugins().length === 0;
},
get children() {
var _el$9 = _tmpl$38(), _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling, _el$10 = _el$1.nextSibling, _el$11 = _el$10.firstChild, _el$13 = _el$11.nextSibling; _el$13.nextSibling;
insert(_el$0, createComponent(PackageIcon, {}));
insert(_el$10, MAX_ACTIVE_PLUGINS, _el$13);
effect((_p$) => {
var _v$13 = styles().pluginsEmptyState, _v$14 = styles().pluginsEmptyStateIcon, _v$15 = styles().pluginsEmptyStateTitle, _v$16 = styles().pluginsEmptyStateHint;
_v$13 !== _p$.e && className(_el$9, _p$.e = _v$13);
_v$14 !== _p$.t && className(_el$0, _p$.t = _v$14);
_v$15 !== _p$.a && className(_el$1, _p$.a = _v$15);
_v$16 !== _p$.o && className(_el$10, _p$.o = _v$16);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0
});
return _el$9;
}
}), null);
effect((_p$) => {
var _v$17 = held() ? "true" : void 0, _v$18 = styles().pluginWorkspace, _v$19 = props.visible ? "block" : "none", _v$20 = styles().pluginSrOnly, _v$21 = styles().pluginSrOnly;
_v$17 !== _p$.e && setAttribute(_el$6, "data-tsd-dragging", _p$.e = _v$17);
_v$18 !== _p$.t && className(_el$6, _p$.t = _v$18);
_v$19 !== _p$.a && setStyleProperty(_el$6, "display", _p$.a = _v$19);
_v$20 !== _p$.o && className(_el$7, _p$.o = _v$20);
_v$21 !== _p$.i && className(_el$8, _p$.i = _v$21);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0
});
return _el$6;
})();
};
delegateEvents(["pointerdown", "keydown", "click", "pointerup"]);
// src/components/tab-content.tsx
var _tmpl$21 = /* @__PURE__ */ template(`<div data-tsd-surface style="flex:1 1 0;min-height:0;position:relative">`);
var TabContent = (props) => {
const {
state
} = createDevtoolsState();
const styles = createStyles();
const component = createMemo(() => tabs.find((tab) => tab.id === state().activeTab)?.component || null);
const showsPlugins = () => !props.showMarketplace && state().activeTab === "plugins";
return (() => {
var _el$ = _tmpl$21();
insert(_el$, createComponent(PluginWorkspace, {
get isOpen() {
return props.isOpen;
},
get visible() {
return showsPlugins();
}
}), null);
insert(_el$, (() => {
var _c$ = memo(() => !!props.showMarketplace);
return () => _c$() ? createComponent(PluginMarketplace, {}) : component()?.({
isOpen: props.isOpen
});
})(), null);
effect(() => className(_el$, styles().tabContent));
return _el$;
})();
};
var _tmpl$30 = /* @__PURE__ */ template(`<div style=pointer-events:none>`);
var SourceInspector = () => {
const {
settings
} = createDevtoolsSettings();
const highlightStateInit = () => ({
element: null,
bounding: {
width: 0,
height: 0,
left: 0,
top: 0
},
dataSource: ""
});
const [highlightState, setHighlightState] = createStore(highlightStateInit());
const resetHighlight = () => {
setHighlightState(highlightStateInit());
};
const [nameTagRef, setNameTagRef] = createSignal(null);
const nameTagSize = createElementSize(() => nameTagRef());
const [mousePosition, setMousePosition] = createStore({
x: 0,
y: 0
});
createEventListener(document, "mousemove", (e) => {
setMousePosition({
x: e.clientX,
y: e.clientY
});
});
const downList = useKeyDownList();
const [disabledAfterClick, setDisabledAfterClick] = createSignal(false);
const isHighlightingKeysHeld = createMemo(() => {
return isHotkeyCombinationPressed(downList(), settings().inspectHotkey);
});
createEffect(() => {
if (!isHighlightingKeysHeld()) {
setDisabledAfterClick(false);
}
});
const isActive = createMemo(() => isHighlightingKeysHeld() && !disabledAfterClick());
createEffect(() => {
if (isActive()) {
document.body.style.cursor = "pointer";
} else {
document.body.style.cursor = "";
}
});
createEffect(() => {
if (!isActive()) {
resetHighlight();
return;
}
const target = document.elementFromPoint(mousePosition.x, mousePosition.y);
if (!(target instanceof HTMLElement)) {
resetHighlight();
return;
}
if (target === highlightState.element) {
return;
}
const dataSource = target.getAttribute("data-tsd-source");
if (!dataSource) {
resetHighlight();
return;
}
const rect = target.getBoundingClientRect();
const bounding = {
width: rect.width,
height: rect.height,
left: rect.left,
top: rect.top
};
setHighlightState({
element: target,
bounding,
dataSource
});
});
createEventListener(document, "click", (e) => {
if (!highlightState.element) return;
const source = highlightState.dataSource;
window.getSelection()?.removeAllRanges();
e.preventDefault();
e.stopPropagation();
setDisabledAfterClick(true);
if (settings().sourceAction === "copy-path") {
navigator.clipboard.writeText(source).catch(() => {
});
return;
}
const baseUrl = new URL(import.meta.env?.BASE_URL ?? "/", location.origin);
const url = new URL(`__tsd/open-source?source=${encodeURIComponent(source)}`, baseUrl);
fetch(url).catch(() => {
});
});
const currentElementBoxStyles = createMemo(() => {
if (highlightState.element) {
return {
display: "block",
width: `${highlightState.bounding.width}px`,
height: `${highlightState.bounding.height}px`,
left: `${highlightState.bounding.left}px`,
top: `${highlightState.bounding.top}px`,
"background-color": "oklch(55.4% 0.046 257.417 /0.25)",
// semantic-color-exempt: source-highlight-alpha
transition: "all 0.05s linear",
position: "fixed",
"z-index": 9999
};
}
return {
display: "none"
};
});
const fileNameStyles = createMemo(() => {
if (highlightState.element && nameTagRef()) {
const windowWidth = window.innerWidth;
const nameTagHeight = nameTagSize.height || 26;
const nameTagWidth = nameTagSize.width || 0;
let left = highlightState.bounding.left;
let top = highlightState.bounding.top - nameTagHeight - 4;
if (top < 0) {
top = highlightState.bounding.top + highlightState.bounding.height + 4;
}
if (left + nameTagWidth > windowWidth) {
left = windowWidth - nameTagWidth - 4;
}
if (left < 0) {
left = 4;
}
return {
position: "fixed",
left: `${left}px`,
top: `${top}px`,
"background-color": "oklch(55.4% 0.046 257.417 /0.80)",
// semantic-color-exempt: source-highlight-alpha
color: "white",
padding: "2px 4px",
fontSize: "12px",
"border-radius": "2px",
"z-index": 1e4,
visibility: "visible",
transition: "all 0.05s linear"
};
}
return {
display: "none"
};
});
return [(() => {
var _el$ = _tmpl$30();
use(setNameTagRef, _el$);
insert(_el$, () => highlightState.dataSource);
effect((_$p) => style(_el$, {
...fileNameStyles()
}, _$p));
return _el$;
})(), (() => {
var _el$2 = _tmpl$30();
effect((_$p) => style(_el$2, {
...currentElementBoxStyles()
}, _$p));
return _el$2;
})()];
};
var _tmpl$31 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 64.04 83.84"fill=currentColor><path d=M32.02,0c23.15,0,32.02,14.33,32.02,41.92s-8.87,41.92-32.02,41.92S0,69.53,0,41.92C0,14.33,8.87,0,32.02,0ZM42.61,61.08c-3.01,0-4.65.82-5.97,1.48-1.14.57-2.03,1.02-4.01,1.02s-2.87-.45-4.01-1.02c-1.32-.66-2.96-1.48-5.97-1.48s-4.65.82-5.97,1.48c-1.14.57-2.03,1.02-4.01,1.02v4.37c3.01,0,4.65-.82,5.97-1.48,1.14-.57,2.03-1.02,4.01-1.02s2.87.45,4.01,1.02c1.32.66,2.96,1.48,5.97,1.48s4.65-.82,5.97-1.48c1.14-.57,2.03-1.02,4.01-1.02s2.87.45,4.01,1.02c1.32.66,2.96,1.48,5.97,1.48v-4.37c-1.97,0-2.87-.45-4.01-1.02-1.32-.66-2.96-1.48-5.97-1.48ZM44.04,17.29c2.27-2.05,0-5.76-2.84-4.64-1.57.63-3.05,1.53-4.37,2.72-2.06,1.87-3.45,4.22-4.2,6.78-.75-2.56-2.14-4.91-4.2-6.78-1.32-1.18-2.8-2.09-4.37-2.72-2.84-1.12-5.11,2.59-2.84,4.64l8.33,7.52c-2.72-1.84-6-2.93-9.54-2.93-1.93,0-3.78.31-5.52.91-2.92.99-2.11,5.35.96,5.35h11.62c-2.56.49-4.99,1.63-7.03,3.45-1.32,1.18-2.36,2.56-3.14,4.07-1.41,2.71,2.05,5.34,4.32,3.31l9.38-8.45-.7,23.25c0,.28-.16.55-.37.78-.3-.13-.58-.27-.9-.43-1.32-.66-2.96-1.48-5.97-1.48s-4.65.82-5.97,1.48c-1.14.57-2.03,1.02-4.01,1.02v4.37c3.01,0,4.65-.82,5.97-1.48,1.14-.57,2.03-1.02,4.01-1.02s2.87.45,4.01,1.02c1.32.66,2.96,1.48,5.97,1.48s4.65-.82,5.97-1.48c1.14-.57,2.03-1.02,4.01-1.02s2.87.45,4.01,1.02c1.32.66,2.96,1.48,5.97,1.48v-4.37c-1.97,0-2.87-.45-4.01-1.02v-.02c-1.32-.66-2.96-1.48-5.97-1.48s-4.65.82-5.97,1.48c-.34.16-.66.33-.99.46-.25-.22-.4-.51-.42-.81l-.7-23.37,9.51,8.58c2.27,2.05,5.73-.58,4.32-3.31-.78-1.5-1.82-2.89-3.14-4.07-2.03-1.84-4.46-2.96-7.03-3.45h11.62c3.08,0,3.87-4.35.96-5.35-1.73-.58-3.59-.91-5.52-.91-3.53,0-6.8,1.09-9.54,2.93l8.33-7.52Z>`);
var _tmpl$213 = /* @__PURE__ */ template(`<title>`);
var TanStackEmblem = (props) => (() => {
var _el$ = _tmpl$31(), _el$2 = _el$.firstChild;
insert(_el$, (() => {
var _c$ = memo(() => !!props.title);
return () => _c$() ? (() => {
var _el$3 = _tmpl$213();
insert(_el$3, () => props.title);
return _el$3;
})() : null;
})(), _el$2);
effect((_p$) => {
var _v$ = props.title ? void 0 : "true", _v$2 = props.title ? "img" : void 0;
_v$ !== _p$.e && setAttribute(_el$, "aria-hidden", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$, "role", _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
})();
// src/components/workbench-header.tsx
var _tmpl$39 = /* @__PURE__ */ template(`<header aria-label="TanStack Devtools"data-testid=workbench-header data-tsd-surface><span data-testid=workbench-logo aria-hidden=true></span><strong data-testid=workbench-wordmark>TanStack Devtools</strong><nav aria-label="Workbench destinations"data-testid=workbench-destinations></nav><span><button type=button aria-label=Settings title=Settings data-testid=tsd-tab-settings data-tsd-control data-icon=cogs>`);
var _tmpl$214 = /* @__PURE__ */ template(`<button type=button data-tsd-control>`);
var _tmpl$310 = /* @__PURE__ */ template(`<button type=button aria-label="Detach TanStack Devtools"title="Detach into its own window"data-testid=tsd-pip-button data-tsd-control>`);
var _tmpl$47 = /* @__PURE__ */ template(`<button type=button aria-label="Close TanStack Devtools"title="Close TanStack Devtools"data-testid=tsd-close-button data-tsd-control>`);
var WorkbenchHeader = (props) => {
const {
state,
setState
} = createDevtoolsState();
const pip = createPiPWindow();
const styles = createStyles();
ensureWorkbenchGeometryStyles(document);
const selectDestination = (destination) => {
if (destination === "marketplace") {
setState({
activeTab: "plugins"
});
props.setShowMarketplace(true);
return;
}
props.setShowMarketplace(false);
setState({
activeTab: destination
});
};
const isSelected = (destination) => destination === "marketplace" ? props.showMarketplace() : !props.showMarketplace() && state().activeTab === destination;
const detach = () => {
pip().requestPipWindow(`width=${window.innerWidth},height=${state().height},top=${window.screen.height},left=${window.screenLeft}`);
};
return (() => {
var _el$ = _tmpl$39(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild;
insert(_el$2, createComponent(TanStackEmblem, {}));
insert(_el$4, () => ["plugins", "marketplace", "seo"].map((destination) => {
const label = destination === "seo" ? "SEO" : destination[0].toUpperCase() + destination.slice(1);
return (() => {
var _el$7 = _tmpl$214();
_el$7.$$click = () => selectDestination(destination);
setAttribute(_el$7, "data-testid", `tsd-tab-${destination}`);
insert(_el$7, label);
effect((_p$) => {
var _v$9 = styles().workbenchNavButton, _v$0 = isSelected(destination) ? "true" : void 0, _v$1 = isSelected(destination) ? "page" : void 0;
_v$9 !== _p$.e && className(_el$7, _p$.e = _v$9);
_v$0 !== _p$.t && setAttribute(_el$7, "data-tsd-selected", _p$.t = _v$0);
_v$1 !== _p$.a && setAttribute(_el$7, "aria-current", _p$.a = _v$1);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$7;
})();
}));
_el$6.$$click = () => selectDestination("settings");
insert(_el$6, createComponent(Cogs, {}));
insert(_el$5, (() => {
var _c$ = memo(() => pip().pipWindow === null);
return () => _c$() ? [(() => {
var _el$8 = _tmpl$310();
_el$8.$$click = detach;
insert(_el$8, createComponent(PiP, {}));
effect(() => className(_el$8, styles().workbenchActionButton));
return _el$8;
})(), (() => {
var _el$9 = _tmpl$47();
addEventListener(_el$9, "click", props.toggleOpen, true);
insert(_el$9, createComponent(X, {}));
effect(() => className(_el$9, styles().workbenchActionButton));
return _el$9;
})()] : null;
})(), null);
effect((_p$) => {
var _v$ = styles().workbenchHeader, _v$2 = styles().workbenchLogo, _v$3 = `${styles().workbenchWordmark} tsd-workbench-wordmark`, _v$4 = styles().workbenchDestinations, _v$5 = styles().workbenchActions, _v$6 = styles().workbenchActionButton, _v$7 = isSelected("settings") ? "true" : void 0, _v$8 = isSelected("settings") ? "page" : void 0;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$3, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$6, _p$.n = _v$6);
_v$7 !== _p$.s && setAttribute(_el$6, "data-tsd-selected", _p$.s = _v$7);
_v$8 !== _p$.h && setAttribute(_el$6, "aria-current", _p$.h = _v$8);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0
});
return _el$;
})();
};
delegateEvents(["click"]);
var _tmpl$40 = /* @__PURE__ */ template(`<h3>`);
var DRAG_HOLD_MS2 = 500;
var PluginsStrip = (props) => {
const {
plugins,
activePlugins,
toggleActivePlugins
} = createPlugins();
const {
setState
} = createDevtoolsState();
const {
beginStripDrag
} = createStripDrag();
const {
setCollapsed
} = createCollapsed();
const {
theme
} = createTheme();
const styles = createStyles();
let suppressClick = false;
const selectPlugin = (pluginId) => {
setState({
activeTab: "plugins"
});
toggleActivePlugins(pluginId);
};
const startStripDrag = (pluginId, event) => {
if (event.button !== 0) return;
suppressClick = false;
let latest = {
x: event.clientX,
y: event.clientY
};
const holdTimer = window.setTimeout(() => {
suppressClick = true;
document.removeEventListener("pointermove", move);
beginStripDrag(pluginId, latest);
}, DRAG_HOLD_MS2);
const move = (moveEvent) => {
latest = {
x: moveEvent.clientX,
y: moveEvent.clientY
};
};
const up = () => {
window.clearTimeout(holdTimer);
document.removeEventListener("pointermove", move);
document.removeEventListener("pointerup", up);
};
document.addEventListener("pointermove", move);
document.addEventListener("pointerup", up);
};
const available = createMemo(() => (plugins() ?? []).filter((plugin) => !activePlugins().includes(plugin.id)));
createEffect((wasEmpty) => {
const isEmpty = available().length === 0;
if (isEmpty && wasEmpty === false) setCollapsed(true);
if (!isEmpty && wasEmpty === true) setCollapsed(false);
return isEmpty;
});
return createComponent(WorkbenchSecondaryTabs, {
ariaLabel: "Plugin panels",
dataTestId: "plugins-strip",
get children() {
return createComponent(For, {
get each() {
return available();
},
children: (plugin) => {
let heading;
const renderName = () => {
if (!heading) return;
if (typeof plugin.name === "string") {
heading.textContent = plugin.name;
} else {
plugin.name(heading, {
theme: theme(),
devtoolsOpen: props.isOpen()
});
}
};
let nameMounted = false;
createEffect(() => {
theme();
props.isOpen();
if (!nameMounted) {
nameMounted = true;
return;
}
renderName();
});
return createComponent(WorkbenchSecondaryTab, {
get ariaLabelledBy() {
return `${PLUGIN_TITLE_CONTAINER_ID}-${plugin.id}`;
},
pluginTitleControl: true,
selected: false,
onPointerDown: (event) => startStripDrag(plugin.id, event),
onClick: () => {
if (suppressClick) return;
selectPlugin(plugin.id);
},
get children() {
var _el$ = _tmpl$40();
use((element) => {
heading = element;
renderName();
}, _el$);
effect((_p$) => {
var _v$ = `${PLUGIN_TITLE_CONTAINER_ID}-${plugin.id}`, _v$2 = typeof plugin.name === "function" ? {
all: "initial"
} : void 0, _v$3 = typeof plugin.name === "string" ? styles().pluginTitleText : void 0;
_v$ !== _p$.e && setAttribute(_el$, "id", _p$.e = _v$);
_p$.t = style(_el$, _v$2, _p$.t);
_v$3 !== _p$.a && className(_el$, _p$.a = _v$3);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$;
}
});
}
});
}
});
};
// src/devtools.tsx
var _tmpl$41 = /* @__PURE__ */ template(`<div>`);
var themeDocumentOwners = /* @__PURE__ */ new WeakMap();
var previousThemeAttributes = /* @__PURE__ */ new WeakMap();
var setDocumentTheme = (document2, owner, theme) => {
let owners = themeDocumentOwners.get(document2);
if (!owners) {
owners = /* @__PURE__ */ new Map();
themeDocumentOwners.set(document2, owners);
previousThemeAttributes.set(document2, document2.documentElement.getAttribute("data-tanstack-devtools-theme"));
}
owners.delete(owner);
owners.set(owner, theme);
document2.documentElement.dataset.tanstackDevtoolsTheme = theme;
};
var clearDocumentTheme = (document2, owner) => {
const owners = themeDocumentOwners.get(document2);
if (!owners) return;
owners.delete(owner);
const themes = [...owners.values()];
const currentTheme = themes[themes.length - 1];
if (currentTheme) {
document2.documentElement.dataset.tanstackDevtoolsTheme = currentTheme;
return;
}
const previousTheme = previousThemeAttributes.get(document2);
if (previousTheme === null || previousTheme === void 0) {
delete document2.documentElement.dataset.tanstackDevtoolsTheme;
} else {
document2.documentElement.dataset.tanstackDevtoolsTheme = previousTheme;
}
themeDocumentOwners.delete(document2);
previousThemeAttributes.delete(document2);
};
function DevTools() {
const {
settings
} = createDevtoolsSettings();
const {
state
} = createDevtoolsState();
const {
setHeight
} = createHeight();
const {
persistOpen,
setPersistOpen
} = createPersistOpen();
const [rootEl, setRootEl] = createSignal();
const [isOpen, setIsOpen] = createSignal(settings().defaultOpen || persistOpen());
const pip = createPiPWindow();
let panelRef;
const [isResizing, setIsResizing] = createSignal(false);
const {
isCollapsed
} = createCollapsed();
const [showMarketplace, setShowMarketplace] = createSignal(false);
const themeOwner = /* @__PURE__ */ Symbol("tanstack-devtools-theme");
const updateHeight = (nextHeight) => {
setHeight(nextHeight);
setIsOpen(nextHeight >= PANEL_CLOSE_THRESHOLD);
};
const toggleOpen = () => {
if (pip().pipWindow) return;
const newState = !isOpen();
setIsOpen(newState);
setPersistOpen(newState);
devtoolsEventClient.emit("trigger-toggled", {
isOpen: newState
});
};
createEffect(() => {
const unsubscribe = devtoolsEventClient.on("trigger-toggled", (event) => {
if (pip().pipWindow) return;
const payload = event.payload;
if (payload.isOpen !== isOpen()) {
setIsOpen(payload.isOpen);
setPersistOpen(payload.isOpen);
}
});
onCleanup(unsubscribe);
});
const showsPluginsStrip = () => state().activeTab === "plugins" && !showMarketplace();
const showsSeoStrip = () => state().activeTab === "seo" && !showMarketplace();
const hasSubheader = () => showsPluginsStrip() || showsSeoStrip();
const handleDragStart = (panelElement, startEvent) => {
if (startEvent.button !== 0 || !panelElement) return;
startEvent.preventDefault();
setIsResizing(true);
const originalHeight = panelElement.getBoundingClientRect().height;
const startY = startEvent.clientY;
const previousUserSelect = document.body.style.userSelect;
document.body.style.userSelect = "none";
const run = (moveEvent) => {
moveEvent.preventDefault();
const delta = startY - moveEvent.clientY;
const newHeight = settings().panelLocation === "bottom" ? originalHeight + delta : originalHeight - delta;
updateHeight(newHeight);
};
const stop = () => {
document.body.style.userSelect = previousUserSelect;
setIsResizing(false);
document.removeEventListener("mousemove", run);
document.removeEventListener("mouseup", stop);
};
document.addEventListener("mousemove", run);
document.addEventListener("mouseup", stop);
};
createEffect(() => {
const onKeyDown = (event) => {
if (event.key === "Escape" && isOpen()) toggleOpen();
};
window.addEventListener("keydown", onKeyDown);
onCleanup(() => window.removeEventListener("keydown", onKeyDown));
});
createDisableTabbing(isOpen);
createEffect(() => {
const element = rootEl();
if (element) {
element.style.setProperty("--tsrd-font-size", getComputedStyle(element).fontSize);
}
});
createEffect(() => {
const isEditableTarget = (element) => {
if (!element || !(element instanceof HTMLElement)) return false;
if (element.isContentEditable) return true;
if (["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName)) return true;
return element.getAttribute("role") === "textbox";
};
for (const permutation of getHotkeyPermutations(settings().openHotkey)) {
createShortcut(permutation, () => {
if (!isEditableTarget(document.activeElement)) toggleOpen();
});
}
});
const {
theme
} = createTheme();
createEffect(() => {
const activeDocument = pip().pipWindow?.document ?? document;
setDocumentTheme(activeDocument, themeOwner, theme());
onCleanup(() => clearDocumentTheme(activeDocument, themeOwner));
});
return createComponent(ThemeContextProvider, {
get theme() {
return theme();
},
get children() {
return createComponent(Portal, {
get mount() {
return (pip().pipWindow ?? window).document.body;
},
get children() {
var _el$ = _tmpl$41();
use(setRootEl, _el$);
setAttribute(_el$, "data-testid", TANSTACK_DEVTOOLS);
insert(_el$, createComponent(Show, {
get when() {
return memo(() => pip().pipWindow !== null)() ? true : memo(() => !!settings().requireUrlFlag)() ? window.location.search.includes(settings().urlFlag) : true;
},
get children() {
return [createComponent(Trigger, {
isOpen,
setIsOpen: toggleOpen
}), createComponent(MainPanel, {
isResizing,
isOpen,
isCollapsed,
hasSubheader,
get children() {
return createComponent(ContentPanel, {
ref: (ref) => panelRef = ref,
handleDragStart: (event) => handleDragStart(panelRef, event),
handleHeightChange: updateHeight,
get children() {
return [createComponent(WorkbenchHeader, {
showMarketplace,
setShowMarketplace,
toggleOpen
}), createComponent(Show, {
get when() {
return showsPluginsStrip();
},
get children() {
return createComponent(PluginsStrip, {
isOpen
});
}
}), createComponent(TabContent, {
get isOpen() {
return isOpen();
},
get showMarketplace() {
return showMarketplace();
}
})];
}
});
}
})];
}
}), null);
insert(_el$, createComponent(SourceInspector, {}), null);
return _el$;
}
});
}
});
}
export { DevTools as default };