🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP →
Sign In

@tanstack/pacer-devtools

Package Overview
Dependencies
Maintainers
3
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/pacer-devtools - npm Package Compare versions

Comparing version
1.3.1
to
1.4.0
+1445
dist/components-BwHXH_1Y.js
import { className, createComponent, delegateEvents, effect, insert, memo, setStyleProperty, style, template, use } from "solid-js/web";
import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, createTheme } from "@tanstack/devtools-ui";
import { createStore, produce } from "solid-js/store";
import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client";
import * as goober from "goober";
import { shallow, useSelector } from "@tanstack/solid-store";
import clsx from "clsx";
import { emitChange } from "@tanstack/pacer";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
//#region src/PacerContextProvider.tsx
const initialPacerDevtoolsStore = {
asyncBatchers: [],
asyncDebouncers: [],
asyncQueuers: [],
asyncRateLimiters: [],
asyncThrottlers: [],
batchers: [],
debouncers: [],
queuers: [],
rateLimiters: [],
throttlers: [],
lastUpdatedByKey: {}
};
const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]);
/**
* Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per
* event suffix so the same `pacer:${suffix}` channel the library emits on is
* observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is
* not always relayed the same way by the shell.
*
* `d-*` suffixes are used when the devtools panel pushes state back (see
* ActionButtons); those must update the panel store too.
*/
const PACER_DEVTOOLS_UTIL_EVENTS = [
{
listKey: "asyncBatchers",
suffixes: ["AsyncBatcher", "d-AsyncBatcher"]
},
{
listKey: "asyncDebouncers",
suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"]
},
{
listKey: "asyncQueuers",
suffixes: ["AsyncQueuer", "d-AsyncQueuer"]
},
{
listKey: "asyncRateLimiters",
suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"]
},
{
listKey: "asyncThrottlers",
suffixes: ["AsyncThrottler", "d-AsyncThrottler"]
},
{
listKey: "batchers",
suffixes: ["Batcher", "d-Batcher"]
},
{
listKey: "debouncers",
suffixes: ["Debouncer", "d-Debouncer"]
},
{
listKey: "queuers",
suffixes: ["Queuer", "d-Queuer"]
},
{
listKey: "rateLimiters",
suffixes: ["RateLimiter", "d-RateLimiter"]
},
{
listKey: "throttlers",
suffixes: ["Throttler", "d-Throttler"]
}
];
function PacerContextProvider(props) {
const [store, setStore] = createStore(initialPacerDevtoolsStore);
createEffect(() => {
const cleanups = [];
for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => {
const key = e.payload.key;
if (!key) return;
const instance = getPacerDevtoolsInstance(key);
if (!instance || typeof instance !== "object") return;
setStore(produce((draft) => {
const list = draft[listKey];
const index = list.findIndex((item) => item.key === key);
const inst = instance;
if (index !== -1) list[index] = inst;
else list.push(inst);
draft.lastUpdatedByKey[key] = Date.now();
}));
}));
onCleanup(() => {
for (const cleanup of cleanups) cleanup();
});
});
return createComponent(PacerDevtoolsContext.Provider, {
value: [store, setStore],
get children() {
return props.children;
}
});
}
const usePacerDevtoolsContext = () => {
return useContext(PacerDevtoolsContext);
};
const usePacerDevtoolsState = () => {
const [state] = usePacerDevtoolsContext();
return state;
};
//#endregion
//#region src/styles/tokens.ts
const tokens = {
colors: {
inherit: "inherit",
current: "currentColor",
transparent: "transparent",
black: "#000000",
white: "#ffffff",
neutral: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
darkGray: {
50: "#525c7a",
100: "#49536e",
200: "#414962",
300: "#394056",
400: "#313749",
500: "#292e3d",
600: "#212530",
700: "#191c24",
800: "#111318",
900: "#0b0d10"
},
gray: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
blue: {
25: "#F5FAFF",
50: "#EFF8FF",
100: "#D1E9FF",
200: "#B2DDFF",
300: "#84CAFF",
400: "#53B1FD",
500: "#2E90FA",
600: "#1570EF",
700: "#175CD3",
800: "#1849A9",
900: "#194185"
},
green: {
25: "#F6FEF9",
50: "#ECFDF3",
100: "#D1FADF",
200: "#A6F4C5",
300: "#6CE9A6",
400: "#32D583",
500: "#12B76A",
600: "#039855",
700: "#027A48",
800: "#05603A",
900: "#054F31"
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a"
},
yellow: {
25: "#FFFCF5",
50: "#FFFAEB",
100: "#FEF0C7",
200: "#FEDF89",
300: "#FEC84B",
400: "#FDB022",
500: "#F79009",
600: "#DC6803",
700: "#B54708",
800: "#93370D",
900: "#7A2E0E"
},
purple: {
25: "#FAFAFF",
50: "#F4F3FF",
100: "#EBE9FE",
200: "#D9D6FE",
300: "#BDB4FE",
400: "#9B8AFB",
500: "#7A5AF8",
600: "#6938EF",
700: "#5925DC",
800: "#4A1FB8",
900: "#3E1C96"
},
teal: {
25: "#F6FEFC",
50: "#F0FDF9",
100: "#CCFBEF",
200: "#99F6E0",
300: "#5FE9D0",
400: "#2ED3B7",
500: "#15B79E",
600: "#0E9384",
700: "#107569",
800: "#125D56",
900: "#134E48"
},
pink: {
25: "#fdf2f8",
50: "#fce7f3",
100: "#fbcfe8",
200: "#f9a8d4",
300: "#f472b6",
400: "#ec4899",
500: "#db2777",
600: "#be185d",
700: "#9d174d",
800: "#831843",
900: "#500724"
},
cyan: {
25: "#ecfeff",
50: "#cffafe",
100: "#a5f3fc",
200: "#67e8f9",
300: "#22d3ee",
400: "#06b6d4",
500: "#0891b2",
600: "#0e7490",
700: "#155e75",
800: "#164e63",
900: "#083344"
}
},
alpha: {
100: "ff",
90: "e5",
80: "cc",
70: "b3",
60: "99",
50: "80",
40: "66",
30: "4d",
20: "33",
10: "1a",
0: "00"
},
font: {
size: {
"2xs": "calc(var(--tsrd-font-size) * 0.625)",
xs: "calc(var(--tsrd-font-size) * 0.75)",
sm: "calc(var(--tsrd-font-size) * 0.875)",
md: "var(--tsrd-font-size)",
lg: "calc(var(--tsrd-font-size) * 1.125)",
xl: "calc(var(--tsrd-font-size) * 1.25)",
"2xl": "calc(var(--tsrd-font-size) * 1.5)",
"3xl": "calc(var(--tsrd-font-size) * 1.875)",
"4xl": "calc(var(--tsrd-font-size) * 2.25)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.75)",
"7xl": "calc(var(--tsrd-font-size) * 4.5)",
"8xl": "calc(var(--tsrd-font-size) * 6)",
"9xl": "calc(var(--tsrd-font-size) * 8)"
},
lineHeight: {
"3xs": "calc(var(--tsrd-font-size) * 0.75)",
"2xs": "calc(var(--tsrd-font-size) * 0.875)",
xs: "calc(var(--tsrd-font-size) * 1)",
sm: "calc(var(--tsrd-font-size) * 1.25)",
md: "calc(var(--tsrd-font-size) * 1.5)",
lg: "calc(var(--tsrd-font-size) * 1.75)",
xl: "calc(var(--tsrd-font-size) * 2)",
"2xl": "calc(var(--tsrd-font-size) * 2.25)",
"3xl": "calc(var(--tsrd-font-size) * 2.5)",
"4xl": "calc(var(--tsrd-font-size) * 2.75)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.25)",
"7xl": "calc(var(--tsrd-font-size) * 3.5)",
"8xl": "calc(var(--tsrd-font-size) * 3.75)",
"9xl": "calc(var(--tsrd-font-size) * 4)"
},
weight: {
thin: "100",
extralight: "200",
light: "300",
normal: "400",
medium: "500",
semibold: "600",
bold: "700",
extrabold: "800",
black: "900"
},
fontFamily: {
sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif",
mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace`
}
},
breakpoints: {
xs: "320px",
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px"
},
border: { radius: {
none: "0px",
xs: "calc(var(--tsrd-font-size) * 0.125)",
sm: "calc(var(--tsrd-font-size) * 0.25)",
md: "calc(var(--tsrd-font-size) * 0.375)",
lg: "calc(var(--tsrd-font-size) * 0.5)",
xl: "calc(var(--tsrd-font-size) * 0.75)",
"2xl": "calc(var(--tsrd-font-size) * 1)",
"3xl": "calc(var(--tsrd-font-size) * 1.5)",
full: "9999px"
} },
size: {
0: "0px",
.25: "calc(var(--tsrd-font-size) * 0.0625)",
.5: "calc(var(--tsrd-font-size) * 0.125)",
1: "calc(var(--tsrd-font-size) * 0.25)",
1.5: "calc(var(--tsrd-font-size) * 0.375)",
2: "calc(var(--tsrd-font-size) * 0.5)",
2.5: "calc(var(--tsrd-font-size) * 0.625)",
3: "calc(var(--tsrd-font-size) * 0.75)",
3.5: "calc(var(--tsrd-font-size) * 0.875)",
4: "calc(var(--tsrd-font-size) * 1)",
4.5: "calc(var(--tsrd-font-size) * 1.125)",
5: "calc(var(--tsrd-font-size) * 1.25)",
5.5: "calc(var(--tsrd-font-size) * 1.375)",
6: "calc(var(--tsrd-font-size) * 1.5)",
6.5: "calc(var(--tsrd-font-size) * 1.625)",
7: "calc(var(--tsrd-font-size) * 1.75)",
8: "calc(var(--tsrd-font-size) * 2)",
9: "calc(var(--tsrd-font-size) * 2.25)",
10: "calc(var(--tsrd-font-size) * 2.5)",
11: "calc(var(--tsrd-font-size) * 2.75)",
12: "calc(var(--tsrd-font-size) * 3)",
14: "calc(var(--tsrd-font-size) * 3.5)",
16: "calc(var(--tsrd-font-size) * 4)",
20: "calc(var(--tsrd-font-size) * 5)",
24: "calc(var(--tsrd-font-size) * 6)",
28: "calc(var(--tsrd-font-size) * 7)",
32: "calc(var(--tsrd-font-size) * 8)",
36: "calc(var(--tsrd-font-size) * 9)",
40: "calc(var(--tsrd-font-size) * 10)",
44: "calc(var(--tsrd-font-size) * 11)",
48: "calc(var(--tsrd-font-size) * 12)",
52: "calc(var(--tsrd-font-size) * 13)",
56: "calc(var(--tsrd-font-size) * 14)",
60: "calc(var(--tsrd-font-size) * 15)",
64: "calc(var(--tsrd-font-size) * 16)",
72: "calc(var(--tsrd-font-size) * 18)",
80: "calc(var(--tsrd-font-size) * 20)",
96: "calc(var(--tsrd-font-size) * 24)"
},
shadow: {
xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`,
sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`,
md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`,
lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`,
xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`,
"2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`,
inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`,
none: () => `none`
},
zIndices: {
hide: -1,
auto: "auto",
base: 0,
docked: 10,
dropdown: 1e3,
sticky: 1100,
banner: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800
}
};
//#endregion
//#region src/styles/use-styles.ts
const stylesFactory = (theme) => {
const { colors, font, size, alpha, border } = tokens;
const { fontFamily, size: fontSize } = font;
const css = goober.css;
const t = (light, dark) => theme === "light" ? light : dark;
return {
/**
* Fills the devtools-utils portal div (height: 100%) and is a flex parent so MainPanel
* gets a real flex height budget (not just height: 100% without a flex chain).
*/
shellRoot: css`
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
`,
/** Overrides devtools-ui MainPanel overflow-y: auto so inner columns own scrolling */
mainPanelShell: css`
display: flex;
flex-direction: column;
flex: 1 1 0%;
min-height: 0;
overflow: hidden !important;
`,
mainContainer: css`
display: flex;
flex: 1 1 0%;
min-height: 0;
overflow: hidden;
padding: ${size[2]};
padding-top: 0;
margin-top: ${size[2]};
`,
dragHandle: css`
width: 8px;
background: ${t(colors.gray[300], colors.darkGray[600])};
cursor: col-resize;
position: relative;
transition: all 0.2s ease;
user-select: none;
pointer-events: all;
margin: 0 ${size[1]};
border-radius: 2px;
&:hover {
background: ${t(colors.blue[600], colors.blue[500])};
margin: 0 ${size[1]};
}
&.dragging {
background: ${t(colors.blue[700], colors.blue[600])};
margin: 0 ${size[1]};
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background: ${t(colors.gray[400], colors.darkGray[400])};
border-radius: 1px;
pointer-events: none;
}
&:hover::after,
&.dragging::after {
background: ${t(colors.blue[500], colors.blue[300])};
}
`,
leftPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex-shrink: 0;
`,
rightPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex: 1;
`,
panelHeader: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
padding: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
background: ${t(colors.gray[100], colors.darkGray[800])};
flex-shrink: 0;
`,
utilList: css`
flex: 1;
overflow-y: auto;
padding: ${size[1]};
min-height: 0;
`,
utilGroup: css`
margin-bottom: ${size[2]};
`,
utilGroupHeader: css`
font-size: ${fontSize.xs};
font-weight: ${font.weight.semibold};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: ${size[1]};
padding: ${size[1]} ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
`,
utilRow: css`
display: flex;
justify-content: space-between;
align-items: center;
padding: ${size[2]};
margin-bottom: ${size[1]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[600])};
border-color: ${t(colors.gray[400], colors.darkGray[500])};
}
`,
utilRowSelected: css`
background: ${t(colors.blue[100], colors.blue[900] + alpha[20])};
border-color: ${t(colors.blue[600], colors.blue[500])};
box-shadow: 0 0 0 1px
${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])};
`,
utilKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
utilStatus: css`
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
padding: ${size[1]} ${size[1]};
background: ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.sm};
margin-left: ${size[1]};
`,
stateDetails: css`
flex: 1;
overflow-y: auto;
padding: ${size[2]};
min-height: 0;
`,
stateHeader: css`
margin-bottom: ${size[2]};
padding-bottom: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
`,
stateTitle: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
margin-bottom: ${size[1]};
`,
stateKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
word-break: break-all;
`,
stateContent: css`
background: ${t(colors.gray[100], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[2]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
detailsGrid: css`
display: grid;
grid-template-columns: 1fr;
gap: ${size[2]};
align-items: start;
`,
detailSection: css`
background: ${t(colors.white, colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.md};
padding: ${size[2]};
`,
detailSectionHeader: css`
font-size: ${fontSize.sm};
font-weight: ${font.weight.bold};
color: ${t(colors.gray[800], colors.gray[200])};
margin-bottom: ${size[1]};
text-transform: uppercase;
letter-spacing: 0.04em;
`,
actionsRow: css`
display: flex;
flex-wrap: wrap;
gap: ${size[2]};
`,
actionButton: css`
display: inline-flex;
align-items: center;
gap: ${size[1]};
padding: ${size[1]} ${size[2]};
border-radius: ${border.radius.md};
border: 1px solid ${t(colors.gray[300], colors.darkGray[500])};
background: ${t(colors.gray[200], colors.darkGray[600])};
color: ${t(colors.gray[900], colors.gray[100])};
font-size: ${fontSize.xs};
cursor: pointer;
user-select: none;
transition:
background 0.15s,
border-color 0.15s;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[500])};
border-color: ${t(colors.gray[400], colors.darkGray[400])};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background: ${t(colors.gray[200], colors.darkGray[600])};
border-color: ${t(colors.gray[300], colors.darkGray[500])};
}
}
`,
actionDotBlue: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.blue[400]};
`,
actionDotGreen: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.green[400]};
`,
actionDotRed: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.red[400]};
`,
actionDotYellow: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.yellow[400]};
`,
actionDotOrange: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.pink[400]};
`,
actionDotPurple: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.purple[400]};
`,
infoGrid: css`
display: grid;
grid-template-columns: auto 1fr;
gap: ${size[1]};
row-gap: ${size[1]};
align-items: center;
`,
infoLabel: css`
color: ${t(colors.gray[600], colors.gray[400])};
font-size: ${fontSize.xs};
text-transform: uppercase;
letter-spacing: 0.05em;
`,
infoValueMono: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
word-break: break-all;
`,
noSelection: css`
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: ${t(colors.gray[500], colors.gray[500])};
font-style: italic;
text-align: center;
padding: ${size[4]};
`,
sectionContainer: css`
display: flex;
flex-wrap: wrap;
gap: ${size[4]};
`,
section: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))};
padding: ${size[4]};
margin-bottom: ${size[4]};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
min-width: 0;
max-width: 33%;
max-height: fit-content;
`,
sectionHeader: css`
font-size: ${fontSize.lg};
font-weight: ${font.weight.bold};
margin-bottom: ${size[2]};
color: ${t(colors.blue[600], colors.blue[400])};
letter-spacing: 0.01em;
display: flex;
align-items: center;
gap: ${size[2]};
`,
sectionEmpty: css`
color: ${t(colors.gray[500], colors.gray[500])};
font-size: ${fontSize.sm};
font-style: italic;
margin: ${size[2]} 0;
`,
instanceList: css`
display: flex;
flex-direction: column;
gap: ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
instanceCard: css`
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[3]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
font-size: ${fontSize.sm};
color: ${t(colors.gray[900], colors.gray[100])};
font-family: ${fontFamily.mono};
overflow-x: auto;
transition:
box-shadow 0.3s,
background 0.3s;
`
};
};
function useStyles() {
const { theme } = createTheme();
const [styles, setStyles] = createSignal(stylesFactory(theme()));
createEffect(() => {
setStyles(stylesFactory(theme()));
});
return styles;
}
//#endregion
//#region src/components/util-groups.ts
const UTIL_GROUPS = [
{
key: "debouncers",
label: "Debouncers",
displayName: "Debouncer"
},
{
key: "throttlers",
label: "Throttlers",
displayName: "Throttler"
},
{
key: "batchers",
label: "Batchers",
displayName: "Batcher"
},
{
key: "queuers",
label: "Queuers",
displayName: "Queuer"
},
{
key: "rateLimiters",
label: "Rate Limiters",
displayName: "Rate Limiter"
},
{
key: "asyncDebouncers",
label: "Async Debouncers",
displayName: "Async Debouncer"
},
{
key: "asyncThrottlers",
label: "Async Throttlers",
displayName: "Async Throttler"
},
{
key: "asyncBatchers",
label: "Async Batchers",
displayName: "Async Batcher"
},
{
key: "asyncQueuers",
label: "Async Queuers",
displayName: "Async Queuer"
},
{
key: "asyncRateLimiters",
label: "Async Rate Limiters",
displayName: "Async Rate Limiter"
}
];
//#endregion
//#region src/components/UtilList.tsx
var _tmpl$$4 = /*#__PURE__*/ template(`<div>`);
var _tmpl$2$3 = /*#__PURE__*/ template(`<div><div>`);
var _tmpl$3$2 = /*#__PURE__*/ template(`<div><div></div><div>`);
function UtilList(props) {
const styles = useStyles();
return (() => {
var _el$ = _tmpl$$4();
insert(_el$, createComponent(For, {
each: UTIL_GROUPS,
children: (group) => memo(() => memo(() => props.utilState()[group.key].length > 0)() && (() => {
var _el$2 = _tmpl$2$3(), _el$3 = _el$2.firstChild;
insert(_el$3, () => group.label);
insert(_el$2, createComponent(For, {
get each() {
return props.utilState()[group.key];
},
children: (instance) => {
const status = (() => {
try {
const statusAccessor = useSelector(instance.store, (s) => s.status);
return () => statusAccessor() ?? "unknown";
} catch {
return () => "unknown";
}
})();
return (() => {
var _el$4 = _tmpl$3$2(), _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling;
_el$4.$$click = () => props.setSelectedKey(instance.key ?? null);
insert(_el$5, () => instance.key);
insert(_el$6, status);
effect((_p$) => {
var _v$3 = clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), _v$4 = styles().utilKey, _v$5 = styles().utilStatus;
_v$3 !== _p$.e && className(_el$4, _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$5, _p$.t = _v$4);
_v$5 !== _p$.a && className(_el$6, _p$.a = _v$5);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$4;
})();
}
}), null);
effect((_p$) => {
var _v$ = styles().utilGroup, _v$2 = styles().utilGroupHeader;
_v$ !== _p$.e && className(_el$2, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$2;
})())
}));
effect(() => className(_el$, styles().utilList));
return _el$;
})();
}
delegateEvents(["click"]);
//#endregion
//#region src/utils/read-pacer-store-state.ts
/** TanStack Store (`@tanstack/store`) used by pacer utils. */
function isPacerUtilTanStackStore(x) {
return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function";
}
/**
* Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may
* also expose a reactive `state` field. Use this for devtools UI that needs a
* plain snapshot (e.g. JsonTree).
*/
function getPacerUtilStoreState(instance) {
const store = instance.store;
if (store && typeof store.get === "function") return store.get();
if (store && store.state !== void 0) return store.state;
return instance.state;
}
//#endregion
//#region src/components/ActionButtons.tsx
var _tmpl$$3 = /*#__PURE__*/ template(`<div>No actions available for this util`);
var _tmpl$2$2 = /*#__PURE__*/ template(`<div>`);
var _tmpl$3$1 = /*#__PURE__*/ template(`<button><span>`);
var _tmpl$4 = /*#__PURE__*/ template(`<button><span></span>Flush`);
var _tmpl$5 = /*#__PURE__*/ template(`<button><span></span>Cancel`);
var _tmpl$6 = /*#__PURE__*/ template(`<button><span></span>Reset`);
var _tmpl$7 = /*#__PURE__*/ template(`<button><span></span>Clear`);
function ActionButtons(props) {
const styles = useStyles();
const utilInstance = props.instance;
const store = utilInstance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(utilInstance);
const getState = () => stateAccessor();
const hasPending = () => {
return "isPending" in getState();
};
const isPending = () => {
const u = getState();
return "isPending" in u ? !!u.isPending : false;
};
const isEmptyFlag = () => {
const u = getState();
return "isEmpty" in u ? !!u.isEmpty : false;
};
const hasFlush = typeof utilInstance.flush === "function";
const hasCancel = typeof utilInstance.cancel === "function";
const hasReset = typeof utilInstance.reset === "function";
const hasClear = typeof utilInstance.clear === "function";
const hasStart = typeof utilInstance.start === "function";
const hasStop = typeof utilInstance.stop === "function";
const hasStartStop = hasStart && hasStop;
const isRunning = () => {
const u = getState();
return "isRunning" in u ? !!u.isRunning : true;
};
if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return (() => {
var _el$ = _tmpl$$3();
effect(() => className(_el$, styles().sectionEmpty));
return _el$;
})();
const emitName = `d-${props.utilName}`;
const utilNameLower = props.utilName.toLowerCase();
return (() => {
var _el$2 = _tmpl$2$2();
insert(_el$2, (() => {
var _c$ = memo(() => !!hasPending());
return () => _c$() && (() => {
var _el$3 = _tmpl$3$1(), _el$4 = _el$3.firstChild;
_el$3.$$mousedown = () => {
const next = !isPending();
utilInstance.store.setState((prev) => ({
...prev,
isPending: next
}));
emitChange(emitName, utilInstance);
};
insert(_el$3, () => isPending() ? "Restore Pending" : "Trigger Pending", null);
effect((_p$) => {
var _v$ = styles().actionButton, _v$2 = styles().actionDotBlue;
_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$2, hasFlush && (() => {
var _el$5 = _tmpl$4(), _el$6 = _el$5.firstChild;
_el$5.$$mousedown = () => {
utilInstance.flush();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$3 = styles().actionButton, _v$4 = utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$5 = styles().actionDotGreen;
_v$3 !== _p$.e && className(_el$5, _p$.e = _v$3);
_v$4 !== _p$.t && (_el$5.disabled = _p$.t = _v$4);
_v$5 !== _p$.a && className(_el$6, _p$.a = _v$5);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$5;
})(), null);
insert(_el$2, hasCancel && (() => {
var _el$7 = _tmpl$5(), _el$8 = _el$7.firstChild;
_el$7.$$mousedown = () => {
utilInstance.cancel();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$6 = styles().actionButton, _v$7 = utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$8 = styles().actionDotRed;
_v$6 !== _p$.e && className(_el$7, _p$.e = _v$6);
_v$7 !== _p$.t && (_el$7.disabled = _p$.t = _v$7);
_v$8 !== _p$.a && className(_el$8, _p$.a = _v$8);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$7;
})(), null);
insert(_el$2, hasReset && (() => {
var _el$9 = _tmpl$6(), _el$0 = _el$9.firstChild;
_el$9.$$mousedown = () => {
utilInstance.reset();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$9 = styles().actionButton, _v$0 = styles().actionDotYellow;
_v$9 !== _p$.e && className(_el$9, _p$.e = _v$9);
_v$0 !== _p$.t && className(_el$0, _p$.t = _v$0);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$9;
})(), null);
insert(_el$2, hasClear && (() => {
var _el$1 = _tmpl$7(), _el$10 = _el$1.firstChild;
_el$1.$$mousedown = () => {
utilInstance.clear();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$1 = styles().actionButton, _v$10 = utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$11 = styles().actionDotOrange;
_v$1 !== _p$.e && className(_el$1, _p$.e = _v$1);
_v$10 !== _p$.t && (_el$1.disabled = _p$.t = _v$10);
_v$11 !== _p$.a && className(_el$10, _p$.a = _v$11);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$1;
})(), null);
insert(_el$2, hasStartStop && (() => {
var _el$11 = _tmpl$3$1(), _el$12 = _el$11.firstChild;
_el$11.$$mousedown = () => {
if (isRunning()) utilInstance.stop();
else utilInstance.start();
emitChange(emitName, utilInstance);
};
insert(_el$11, () => isRunning() ? "Stop" : "Start", null);
effect((_p$) => {
var _v$12 = styles().actionButton, _v$13 = styles().actionDotPurple;
_v$12 !== _p$.e && className(_el$11, _p$.e = _v$12);
_v$13 !== _p$.t && className(_el$12, _p$.t = _v$13);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$11;
})(), null);
effect(() => className(_el$2, styles().actionsRow));
return _el$2;
})();
}
delegateEvents(["mousedown"]);
//#endregion
//#region src/components/StateHeader.tsx
var _tmpl$$2 = /*#__PURE__*/ template(`<div><div></div><div style=display:flex;align-items:center;gap:16px><div><div>Key</div><div></div><div>Last Updated</div><div> (<!>)</div></div><div style=margin-left:auto;font-weight:bold>% reduction`);
dayjs.extend(relativeTime);
function reductionFromState(entry, state) {
if (!state) return 0;
const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0;
if (entry.type.toLowerCase().includes("batcher")) {
const totalItemsProcessed = Number(state.totalItemsProcessed) || 0;
if (totalItemsProcessed === 0) return 0;
return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
}
let requestCount = 0;
if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0;
else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0;
else return 0;
if (requestCount === 0) return 0;
const reduction = requestCount - completedExecutions;
return Math.round(reduction / requestCount * 100);
}
function StateHeaderInner(props) {
const key = props.entry.instance.key;
const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now();
const getRelativeTime = () => {
const at = updatedAt();
const diffMs = props.now() - at;
const diffSeconds = Math.floor(diffMs / 1e3);
if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
return dayjs(at).fromNow();
};
const store = props.entry.instance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(props.entry.instance);
const reductionPercentage = () => reductionFromState(props.entry, stateAccessor());
return (() => {
var _el$ = _tmpl$$2(), _el$2 = _el$.firstChild, _el$4 = _el$2.nextSibling.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.firstChild, _el$1 = _el$9.nextSibling;
_el$1.nextSibling;
var _el$10 = _el$4.nextSibling, _el$11 = _el$10.firstChild;
insert(_el$2, () => props.entry.type);
insert(_el$6, key);
insert(_el$8, () => new Date(updatedAt()).toLocaleTimeString(), _el$9);
insert(_el$8, getRelativeTime, _el$1);
insert(_el$10, reductionPercentage, _el$11);
effect((_p$) => {
var _v$ = props.styles.stateHeader, _v$2 = props.styles.stateTitle, _v$3 = props.styles.infoGrid, _v$4 = props.styles.infoLabel, _v$5 = props.styles.infoValueMono, _v$6 = props.styles.infoLabel, _v$7 = props.styles.infoValueMono, _v$8 = props.styles.infoValueMono;
_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$4, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$5, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$6, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$7, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$8, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$10, _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 StateHeader(props) {
const styles = useStyles();
const [now, setNow] = createSignal(Date.now());
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now());
}, 1e3);
onCleanup(() => {
clearInterval(interval);
});
});
return createComponent(Show, {
get when() {
return props.selectedInstance();
},
children: (entry) => createComponent(StateHeaderInner, {
get entry() {
return entry();
},
get styles() {
return styles();
},
now,
get utilState() {
return props.utilState;
}
})
});
}
//#endregion
//#region src/components/UtilStateJsonTree.tsx
/**
* Subscribes to the util's TanStack Store so the tree updates live (same
* idea as {@link UtilList} status). A plain snapshot would only refresh when the
* devtools shell re-renders, which does not happen on nested store updates.
*/
function UtilStateJsonTree(props) {
const store = props.instance?.store;
if (isPacerUtilTanStackStore(store)) {
const snapshot = useSelector(store, (s) => s);
return createComponent(JsonTree, { get value() {
return snapshot();
} });
}
return createComponent(JsonTree, { get value() {
return getPacerUtilStoreState(props.instance);
} });
}
//#endregion
//#region src/components/DetailsPanel.tsx
var _tmpl$$1 = /*#__PURE__*/ template(`<div>`);
var _tmpl$2$1 = /*#__PURE__*/ template(`<div>Select a util from the left panel to view its state`);
var _tmpl$3 = /*#__PURE__*/ template(`<div><div><div>Actions</div></div><div><div>State</div><div></div></div><div><div>Options</div><div>`);
function DetailsPanel(props) {
const styles = useStyles();
return (() => {
var _el$ = _tmpl$$1();
insert(_el$, () => {
const entry = props.selectedInstance();
if (!entry) return (() => {
var _el$2 = _tmpl$2$1();
effect(() => className(_el$2, styles().noSelection));
return _el$2;
})();
return [createComponent(StateHeader, {
get selectedInstance() {
return props.selectedInstance;
},
get utilState() {
return props.utilState;
}
}), (() => {
var _el$3 = _tmpl$3(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$4.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$9 = _el$6.nextSibling, _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling;
insert(_el$4, createComponent(ActionButtons, {
get instance() {
return entry.instance;
},
get utilName() {
return entry.type;
}
}), null);
insert(_el$8, createComponent(UtilStateJsonTree, { get instance() {
return entry.instance;
} }));
insert(_el$1, createComponent(JsonTree, { get value() {
return entry.instance.options;
} }));
effect((_p$) => {
var _v$ = styles().detailsGrid, _v$2 = styles().detailSection, _v$3 = styles().detailSectionHeader, _v$4 = styles().detailSection, _v$5 = styles().detailSectionHeader, _v$6 = styles().stateContent, _v$7 = styles().detailSection, _v$8 = styles().detailSectionHeader, _v$9 = styles().stateContent;
_v$ !== _p$.e && className(_el$3, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$4, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$5, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$6, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$7, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$8, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$9, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$0, _p$.h = _v$8);
_v$9 !== _p$.r && className(_el$1, _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
});
return _el$3;
})()];
});
effect(() => className(_el$, styles().stateDetails));
return _el$;
})();
}
//#endregion
//#region src/components/Shell.tsx
var _tmpl$ = /*#__PURE__*/ template(`<div><div style=min-width:150px;max-width:800px></div><div></div><div style=flex:1><div>Details`);
var _tmpl$2 = /*#__PURE__*/ template(`<div>`);
function readResizeObserverBlockSize(entry) {
const [box] = entry.borderBoxSize;
if (box !== void 0) return box.blockSize;
return entry.contentRect.height;
}
/** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
function resolvePluginHost(el) {
const byId = el.closest("[id^=\"plugin-container-\"]");
if (byId) return byId;
return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement;
}
function Shell() {
const styles = useStyles();
const state = usePacerDevtoolsState();
const utilState = () => state;
const [selectedKey, setSelectedKey] = createSignal(null);
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300);
const [isDragging, setIsDragging] = createSignal(false);
const [shellRootEl, setShellRootEl] = createSignal(null);
const [slotHeightPx, setSlotHeightPx] = createSignal(void 0);
const selectedInstance = createMemo(() => {
const key = selectedKey();
if (!key) return null;
for (const group of UTIL_GROUPS) {
const instance = utilState()[group.key].find((inst) => inst.key === key);
if (instance) return {
instance,
type: group.displayName
};
}
return null;
});
let dragStartX = 0;
let dragStartWidth = 0;
const handleMouseDown = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
dragStartX = e.clientX;
dragStartWidth = leftPanelWidth();
};
const handleMouseMove = (e) => {
if (!isDragging()) return;
e.preventDefault();
const deltaX = e.clientX - dragStartX;
const newWidth = Math.max(150, Math.min(800, dragStartWidth + deltaX));
setLeftPanelWidth(newWidth);
};
const handleMouseUp = () => {
setIsDragging(false);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
onMount(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
});
onCleanup(() => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
});
/**
* TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
* ancestors, so percentage height often never constrains our tree. Observing the
* outer `plugin-container-*` host (not the immediate React mount wrapper) and
* setting an explicit pixel height makes inner overflow-y regions scroll without
* capping to the mount div’s collapsed height.
*/
createEffect(() => {
const el = shellRootEl();
if (!el || typeof ResizeObserver === "undefined") return;
const pluginSlot = resolvePluginHost(el);
if (!pluginSlot) return;
const ro = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) return;
const target = entry.target;
const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry);
if (h > 0) setSlotHeightPx(Math.round(h));
});
ro.observe(pluginSlot);
onCleanup(() => ro.disconnect());
});
return (() => {
var _el$ = _tmpl$2();
use(setShellRootEl, _el$);
insert(_el$, createComponent(MainPanel, {
get ["class"]() {
return styles().mainPanelShell;
},
get children() {
return [createComponent(Header, { get children() {
return createComponent(HeaderLogo, {
flavor: {
light: "#84cc16",
dark: "#84cc16"
},
children: "TanStack Pacer"
});
} }), (() => {
var _el$2 = _tmpl$(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild;
insert(_el$3, createComponent(UtilList, {
selectedKey,
setSelectedKey,
utilState
}));
_el$4.$$mousedown = handleMouseDown;
insert(_el$5, createComponent(DetailsPanel, {
selectedInstance,
utilState
}), null);
effect((_p$) => {
var _v$ = styles().mainContainer, _v$2 = styles().leftPanel, _v$3 = `${leftPanelWidth()}px`, _v$4 = `${styles().dragHandle} ${isDragging() ? "dragging" : ""}`, _v$5 = styles().rightPanel, _v$6 = styles().panelHeader;
_v$ !== _p$.e && className(_el$2, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
_v$3 !== _p$.a && setStyleProperty(_el$3, "width", _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);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$2;
})()];
}
}));
effect((_p$) => {
var _v$7 = styles().shellRoot, _v$8 = { ...slotHeightPx() !== void 0 ? {
height: `${slotHeightPx()}px`,
"max-height": `${slotHeightPx()}px`
} : {} };
_v$7 !== _p$.e && className(_el$, _p$.e = _v$7);
_p$.t = style(_el$, _v$8, _p$.t);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
})();
}
delegateEvents(["mousedown"]);
//#endregion
//#region src/components/index.tsx
function PacerDevtools(props) {
return createComponent(ThemeContextProvider, {
get theme() {
return props.theme;
},
get children() {
return createComponent(PacerContextProvider, { get children() {
return createComponent(Shell, {});
} });
}
});
}
//#endregion
export { PacerDevtools as default };
import { createComponent, escape, ssr, ssrAttribute, ssrStyle, ssrStyleProperty } from "solid-js/web";
import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, createTheme } from "@tanstack/devtools-ui";
import { createStore, produce } from "solid-js/store";
import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client";
import * as goober from "goober";
import { shallow, useSelector } from "@tanstack/solid-store";
import clsx from "clsx";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
//#region src/PacerContextProvider.tsx
const initialPacerDevtoolsStore = {
asyncBatchers: [],
asyncDebouncers: [],
asyncQueuers: [],
asyncRateLimiters: [],
asyncThrottlers: [],
batchers: [],
debouncers: [],
queuers: [],
rateLimiters: [],
throttlers: [],
lastUpdatedByKey: {}
};
const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]);
/**
* Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per
* event suffix so the same `pacer:${suffix}` channel the library emits on is
* observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is
* not always relayed the same way by the shell.
*
* `d-*` suffixes are used when the devtools panel pushes state back (see
* ActionButtons); those must update the panel store too.
*/
const PACER_DEVTOOLS_UTIL_EVENTS = [
{
listKey: "asyncBatchers",
suffixes: ["AsyncBatcher", "d-AsyncBatcher"]
},
{
listKey: "asyncDebouncers",
suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"]
},
{
listKey: "asyncQueuers",
suffixes: ["AsyncQueuer", "d-AsyncQueuer"]
},
{
listKey: "asyncRateLimiters",
suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"]
},
{
listKey: "asyncThrottlers",
suffixes: ["AsyncThrottler", "d-AsyncThrottler"]
},
{
listKey: "batchers",
suffixes: ["Batcher", "d-Batcher"]
},
{
listKey: "debouncers",
suffixes: ["Debouncer", "d-Debouncer"]
},
{
listKey: "queuers",
suffixes: ["Queuer", "d-Queuer"]
},
{
listKey: "rateLimiters",
suffixes: ["RateLimiter", "d-RateLimiter"]
},
{
listKey: "throttlers",
suffixes: ["Throttler", "d-Throttler"]
}
];
function PacerContextProvider(props) {
const [store, setStore] = createStore(initialPacerDevtoolsStore);
createEffect(() => {
const cleanups = [];
for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => {
const key = e.payload.key;
if (!key) return;
const instance = getPacerDevtoolsInstance(key);
if (!instance || typeof instance !== "object") return;
setStore(produce((draft) => {
const list = draft[listKey];
const index = list.findIndex((item) => item.key === key);
const inst = instance;
if (index !== -1) list[index] = inst;
else list.push(inst);
draft.lastUpdatedByKey[key] = Date.now();
}));
}));
onCleanup(() => {
for (const cleanup of cleanups) cleanup();
});
});
return createComponent(PacerDevtoolsContext.Provider, {
value: [store, setStore],
get children() {
return props.children;
}
});
}
const usePacerDevtoolsContext = () => {
return useContext(PacerDevtoolsContext);
};
const usePacerDevtoolsState = () => {
const [state] = usePacerDevtoolsContext();
return state;
};
//#endregion
//#region src/styles/tokens.ts
const tokens = {
colors: {
inherit: "inherit",
current: "currentColor",
transparent: "transparent",
black: "#000000",
white: "#ffffff",
neutral: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
darkGray: {
50: "#525c7a",
100: "#49536e",
200: "#414962",
300: "#394056",
400: "#313749",
500: "#292e3d",
600: "#212530",
700: "#191c24",
800: "#111318",
900: "#0b0d10"
},
gray: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
blue: {
25: "#F5FAFF",
50: "#EFF8FF",
100: "#D1E9FF",
200: "#B2DDFF",
300: "#84CAFF",
400: "#53B1FD",
500: "#2E90FA",
600: "#1570EF",
700: "#175CD3",
800: "#1849A9",
900: "#194185"
},
green: {
25: "#F6FEF9",
50: "#ECFDF3",
100: "#D1FADF",
200: "#A6F4C5",
300: "#6CE9A6",
400: "#32D583",
500: "#12B76A",
600: "#039855",
700: "#027A48",
800: "#05603A",
900: "#054F31"
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a"
},
yellow: {
25: "#FFFCF5",
50: "#FFFAEB",
100: "#FEF0C7",
200: "#FEDF89",
300: "#FEC84B",
400: "#FDB022",
500: "#F79009",
600: "#DC6803",
700: "#B54708",
800: "#93370D",
900: "#7A2E0E"
},
purple: {
25: "#FAFAFF",
50: "#F4F3FF",
100: "#EBE9FE",
200: "#D9D6FE",
300: "#BDB4FE",
400: "#9B8AFB",
500: "#7A5AF8",
600: "#6938EF",
700: "#5925DC",
800: "#4A1FB8",
900: "#3E1C96"
},
teal: {
25: "#F6FEFC",
50: "#F0FDF9",
100: "#CCFBEF",
200: "#99F6E0",
300: "#5FE9D0",
400: "#2ED3B7",
500: "#15B79E",
600: "#0E9384",
700: "#107569",
800: "#125D56",
900: "#134E48"
},
pink: {
25: "#fdf2f8",
50: "#fce7f3",
100: "#fbcfe8",
200: "#f9a8d4",
300: "#f472b6",
400: "#ec4899",
500: "#db2777",
600: "#be185d",
700: "#9d174d",
800: "#831843",
900: "#500724"
},
cyan: {
25: "#ecfeff",
50: "#cffafe",
100: "#a5f3fc",
200: "#67e8f9",
300: "#22d3ee",
400: "#06b6d4",
500: "#0891b2",
600: "#0e7490",
700: "#155e75",
800: "#164e63",
900: "#083344"
}
},
alpha: {
100: "ff",
90: "e5",
80: "cc",
70: "b3",
60: "99",
50: "80",
40: "66",
30: "4d",
20: "33",
10: "1a",
0: "00"
},
font: {
size: {
"2xs": "calc(var(--tsrd-font-size) * 0.625)",
xs: "calc(var(--tsrd-font-size) * 0.75)",
sm: "calc(var(--tsrd-font-size) * 0.875)",
md: "var(--tsrd-font-size)",
lg: "calc(var(--tsrd-font-size) * 1.125)",
xl: "calc(var(--tsrd-font-size) * 1.25)",
"2xl": "calc(var(--tsrd-font-size) * 1.5)",
"3xl": "calc(var(--tsrd-font-size) * 1.875)",
"4xl": "calc(var(--tsrd-font-size) * 2.25)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.75)",
"7xl": "calc(var(--tsrd-font-size) * 4.5)",
"8xl": "calc(var(--tsrd-font-size) * 6)",
"9xl": "calc(var(--tsrd-font-size) * 8)"
},
lineHeight: {
"3xs": "calc(var(--tsrd-font-size) * 0.75)",
"2xs": "calc(var(--tsrd-font-size) * 0.875)",
xs: "calc(var(--tsrd-font-size) * 1)",
sm: "calc(var(--tsrd-font-size) * 1.25)",
md: "calc(var(--tsrd-font-size) * 1.5)",
lg: "calc(var(--tsrd-font-size) * 1.75)",
xl: "calc(var(--tsrd-font-size) * 2)",
"2xl": "calc(var(--tsrd-font-size) * 2.25)",
"3xl": "calc(var(--tsrd-font-size) * 2.5)",
"4xl": "calc(var(--tsrd-font-size) * 2.75)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.25)",
"7xl": "calc(var(--tsrd-font-size) * 3.5)",
"8xl": "calc(var(--tsrd-font-size) * 3.75)",
"9xl": "calc(var(--tsrd-font-size) * 4)"
},
weight: {
thin: "100",
extralight: "200",
light: "300",
normal: "400",
medium: "500",
semibold: "600",
bold: "700",
extrabold: "800",
black: "900"
},
fontFamily: {
sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif",
mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace`
}
},
breakpoints: {
xs: "320px",
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px"
},
border: { radius: {
none: "0px",
xs: "calc(var(--tsrd-font-size) * 0.125)",
sm: "calc(var(--tsrd-font-size) * 0.25)",
md: "calc(var(--tsrd-font-size) * 0.375)",
lg: "calc(var(--tsrd-font-size) * 0.5)",
xl: "calc(var(--tsrd-font-size) * 0.75)",
"2xl": "calc(var(--tsrd-font-size) * 1)",
"3xl": "calc(var(--tsrd-font-size) * 1.5)",
full: "9999px"
} },
size: {
0: "0px",
.25: "calc(var(--tsrd-font-size) * 0.0625)",
.5: "calc(var(--tsrd-font-size) * 0.125)",
1: "calc(var(--tsrd-font-size) * 0.25)",
1.5: "calc(var(--tsrd-font-size) * 0.375)",
2: "calc(var(--tsrd-font-size) * 0.5)",
2.5: "calc(var(--tsrd-font-size) * 0.625)",
3: "calc(var(--tsrd-font-size) * 0.75)",
3.5: "calc(var(--tsrd-font-size) * 0.875)",
4: "calc(var(--tsrd-font-size) * 1)",
4.5: "calc(var(--tsrd-font-size) * 1.125)",
5: "calc(var(--tsrd-font-size) * 1.25)",
5.5: "calc(var(--tsrd-font-size) * 1.375)",
6: "calc(var(--tsrd-font-size) * 1.5)",
6.5: "calc(var(--tsrd-font-size) * 1.625)",
7: "calc(var(--tsrd-font-size) * 1.75)",
8: "calc(var(--tsrd-font-size) * 2)",
9: "calc(var(--tsrd-font-size) * 2.25)",
10: "calc(var(--tsrd-font-size) * 2.5)",
11: "calc(var(--tsrd-font-size) * 2.75)",
12: "calc(var(--tsrd-font-size) * 3)",
14: "calc(var(--tsrd-font-size) * 3.5)",
16: "calc(var(--tsrd-font-size) * 4)",
20: "calc(var(--tsrd-font-size) * 5)",
24: "calc(var(--tsrd-font-size) * 6)",
28: "calc(var(--tsrd-font-size) * 7)",
32: "calc(var(--tsrd-font-size) * 8)",
36: "calc(var(--tsrd-font-size) * 9)",
40: "calc(var(--tsrd-font-size) * 10)",
44: "calc(var(--tsrd-font-size) * 11)",
48: "calc(var(--tsrd-font-size) * 12)",
52: "calc(var(--tsrd-font-size) * 13)",
56: "calc(var(--tsrd-font-size) * 14)",
60: "calc(var(--tsrd-font-size) * 15)",
64: "calc(var(--tsrd-font-size) * 16)",
72: "calc(var(--tsrd-font-size) * 18)",
80: "calc(var(--tsrd-font-size) * 20)",
96: "calc(var(--tsrd-font-size) * 24)"
},
shadow: {
xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`,
sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`,
md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`,
lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`,
xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`,
"2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`,
inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`,
none: () => `none`
},
zIndices: {
hide: -1,
auto: "auto",
base: 0,
docked: 10,
dropdown: 1e3,
sticky: 1100,
banner: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800
}
};
//#endregion
//#region src/styles/use-styles.ts
const stylesFactory = (theme) => {
const { colors, font, size, alpha, border } = tokens;
const { fontFamily, size: fontSize } = font;
const css = goober.css;
const t = (light, dark) => theme === "light" ? light : dark;
return {
/**
* Fills the devtools-utils portal div (height: 100%) and is a flex parent so MainPanel
* gets a real flex height budget (not just height: 100% without a flex chain).
*/
shellRoot: css`
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
`,
/** Overrides devtools-ui MainPanel overflow-y: auto so inner columns own scrolling */
mainPanelShell: css`
display: flex;
flex-direction: column;
flex: 1 1 0%;
min-height: 0;
overflow: hidden !important;
`,
mainContainer: css`
display: flex;
flex: 1 1 0%;
min-height: 0;
overflow: hidden;
padding: ${size[2]};
padding-top: 0;
margin-top: ${size[2]};
`,
dragHandle: css`
width: 8px;
background: ${t(colors.gray[300], colors.darkGray[600])};
cursor: col-resize;
position: relative;
transition: all 0.2s ease;
user-select: none;
pointer-events: all;
margin: 0 ${size[1]};
border-radius: 2px;
&:hover {
background: ${t(colors.blue[600], colors.blue[500])};
margin: 0 ${size[1]};
}
&.dragging {
background: ${t(colors.blue[700], colors.blue[600])};
margin: 0 ${size[1]};
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background: ${t(colors.gray[400], colors.darkGray[400])};
border-radius: 1px;
pointer-events: none;
}
&:hover::after,
&.dragging::after {
background: ${t(colors.blue[500], colors.blue[300])};
}
`,
leftPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex-shrink: 0;
`,
rightPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex: 1;
`,
panelHeader: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
padding: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
background: ${t(colors.gray[100], colors.darkGray[800])};
flex-shrink: 0;
`,
utilList: css`
flex: 1;
overflow-y: auto;
padding: ${size[1]};
min-height: 0;
`,
utilGroup: css`
margin-bottom: ${size[2]};
`,
utilGroupHeader: css`
font-size: ${fontSize.xs};
font-weight: ${font.weight.semibold};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: ${size[1]};
padding: ${size[1]} ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
`,
utilRow: css`
display: flex;
justify-content: space-between;
align-items: center;
padding: ${size[2]};
margin-bottom: ${size[1]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[600])};
border-color: ${t(colors.gray[400], colors.darkGray[500])};
}
`,
utilRowSelected: css`
background: ${t(colors.blue[100], colors.blue[900] + alpha[20])};
border-color: ${t(colors.blue[600], colors.blue[500])};
box-shadow: 0 0 0 1px
${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])};
`,
utilKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
utilStatus: css`
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
padding: ${size[1]} ${size[1]};
background: ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.sm};
margin-left: ${size[1]};
`,
stateDetails: css`
flex: 1;
overflow-y: auto;
padding: ${size[2]};
min-height: 0;
`,
stateHeader: css`
margin-bottom: ${size[2]};
padding-bottom: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
`,
stateTitle: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
margin-bottom: ${size[1]};
`,
stateKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
word-break: break-all;
`,
stateContent: css`
background: ${t(colors.gray[100], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[2]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
detailsGrid: css`
display: grid;
grid-template-columns: 1fr;
gap: ${size[2]};
align-items: start;
`,
detailSection: css`
background: ${t(colors.white, colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.md};
padding: ${size[2]};
`,
detailSectionHeader: css`
font-size: ${fontSize.sm};
font-weight: ${font.weight.bold};
color: ${t(colors.gray[800], colors.gray[200])};
margin-bottom: ${size[1]};
text-transform: uppercase;
letter-spacing: 0.04em;
`,
actionsRow: css`
display: flex;
flex-wrap: wrap;
gap: ${size[2]};
`,
actionButton: css`
display: inline-flex;
align-items: center;
gap: ${size[1]};
padding: ${size[1]} ${size[2]};
border-radius: ${border.radius.md};
border: 1px solid ${t(colors.gray[300], colors.darkGray[500])};
background: ${t(colors.gray[200], colors.darkGray[600])};
color: ${t(colors.gray[900], colors.gray[100])};
font-size: ${fontSize.xs};
cursor: pointer;
user-select: none;
transition:
background 0.15s,
border-color 0.15s;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[500])};
border-color: ${t(colors.gray[400], colors.darkGray[400])};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background: ${t(colors.gray[200], colors.darkGray[600])};
border-color: ${t(colors.gray[300], colors.darkGray[500])};
}
}
`,
actionDotBlue: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.blue[400]};
`,
actionDotGreen: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.green[400]};
`,
actionDotRed: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.red[400]};
`,
actionDotYellow: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.yellow[400]};
`,
actionDotOrange: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.pink[400]};
`,
actionDotPurple: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.purple[400]};
`,
infoGrid: css`
display: grid;
grid-template-columns: auto 1fr;
gap: ${size[1]};
row-gap: ${size[1]};
align-items: center;
`,
infoLabel: css`
color: ${t(colors.gray[600], colors.gray[400])};
font-size: ${fontSize.xs};
text-transform: uppercase;
letter-spacing: 0.05em;
`,
infoValueMono: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
word-break: break-all;
`,
noSelection: css`
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: ${t(colors.gray[500], colors.gray[500])};
font-style: italic;
text-align: center;
padding: ${size[4]};
`,
sectionContainer: css`
display: flex;
flex-wrap: wrap;
gap: ${size[4]};
`,
section: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))};
padding: ${size[4]};
margin-bottom: ${size[4]};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
min-width: 0;
max-width: 33%;
max-height: fit-content;
`,
sectionHeader: css`
font-size: ${fontSize.lg};
font-weight: ${font.weight.bold};
margin-bottom: ${size[2]};
color: ${t(colors.blue[600], colors.blue[400])};
letter-spacing: 0.01em;
display: flex;
align-items: center;
gap: ${size[2]};
`,
sectionEmpty: css`
color: ${t(colors.gray[500], colors.gray[500])};
font-size: ${fontSize.sm};
font-style: italic;
margin: ${size[2]} 0;
`,
instanceList: css`
display: flex;
flex-direction: column;
gap: ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
instanceCard: css`
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[3]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
font-size: ${fontSize.sm};
color: ${t(colors.gray[900], colors.gray[100])};
font-family: ${fontFamily.mono};
overflow-x: auto;
transition:
box-shadow 0.3s,
background 0.3s;
`
};
};
function useStyles() {
const { theme } = createTheme();
const [styles, setStyles] = createSignal(stylesFactory(theme()));
createEffect(() => {
setStyles(stylesFactory(theme()));
});
return styles;
}
//#endregion
//#region src/components/util-groups.ts
const UTIL_GROUPS = [
{
key: "debouncers",
label: "Debouncers",
displayName: "Debouncer"
},
{
key: "throttlers",
label: "Throttlers",
displayName: "Throttler"
},
{
key: "batchers",
label: "Batchers",
displayName: "Batcher"
},
{
key: "queuers",
label: "Queuers",
displayName: "Queuer"
},
{
key: "rateLimiters",
label: "Rate Limiters",
displayName: "Rate Limiter"
},
{
key: "asyncDebouncers",
label: "Async Debouncers",
displayName: "Async Debouncer"
},
{
key: "asyncThrottlers",
label: "Async Throttlers",
displayName: "Async Throttler"
},
{
key: "asyncBatchers",
label: "Async Batchers",
displayName: "Async Batcher"
},
{
key: "asyncQueuers",
label: "Async Queuers",
displayName: "Async Queuer"
},
{
key: "asyncRateLimiters",
label: "Async Rate Limiters",
displayName: "Async Rate Limiter"
}
];
//#endregion
//#region src/components/UtilList.tsx
var _tmpl$$4 = [
"<div",
">",
"</div>"
];
var _tmpl$2$3 = [
"<div",
"><div",
">",
"</div>",
"</div>"
];
var _tmpl$3$2 = [
"<div",
"><div",
">",
"</div><div",
">",
"</div></div>"
];
function UtilList(props) {
const styles = useStyles();
return ssr(_tmpl$$4, ssrAttribute("class", escape(styles().utilList, true), false), escape(createComponent(For, {
each: UTIL_GROUPS,
children: (group) => props.utilState()[group.key].length > 0 && ssr(_tmpl$2$3, ssrAttribute("class", escape(styles().utilGroup, true), false), ssrAttribute("class", escape(styles().utilGroupHeader, true), false), escape(group.label), escape(createComponent(For, {
get each() {
return props.utilState()[group.key];
},
children: (instance) => {
const status = (() => {
try {
const statusAccessor = useSelector(instance.store, (s) => s.status);
return () => statusAccessor() ?? "unknown";
} catch {
return () => "unknown";
}
})();
return ssr(_tmpl$3$2, ssrAttribute("class", escape(clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), true), false), ssrAttribute("class", escape(styles().utilKey, true), false), escape(instance.key), ssrAttribute("class", escape(styles().utilStatus, true), false), escape(status()));
}
})))
})));
}
//#endregion
//#region src/utils/read-pacer-store-state.ts
/** TanStack Store (`@tanstack/store`) used by pacer utils. */
function isPacerUtilTanStackStore(x) {
return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function";
}
/**
* Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may
* also expose a reactive `state` field. Use this for devtools UI that needs a
* plain snapshot (e.g. JsonTree).
*/
function getPacerUtilStoreState(instance) {
const store = instance.store;
if (store && typeof store.get === "function") return store.get();
if (store && store.state !== void 0) return store.state;
return instance.state;
}
//#endregion
//#region src/components/ActionButtons.tsx
var _tmpl$$3 = ["<div", ">No actions available for this util</div>"];
var _tmpl$2$2 = [
"<div",
">",
"",
"",
"",
"",
"",
"</div>"
];
var _tmpl$3$1 = [
"<button",
"><span",
"></span>",
"</button>"
];
var _tmpl$4 = [
"<button",
"",
"><span",
"></span>Flush</button>"
];
var _tmpl$5 = [
"<button",
"",
"><span",
"></span>Cancel</button>"
];
var _tmpl$6 = [
"<button",
"><span",
"></span>Reset</button>"
];
var _tmpl$7 = [
"<button",
"",
"><span",
"></span>Clear</button>"
];
function ActionButtons(props) {
const styles = useStyles();
const utilInstance = props.instance;
const store = utilInstance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(utilInstance);
const getState = () => stateAccessor();
const hasPending = () => {
return "isPending" in getState();
};
const isPending = () => {
const u = getState();
return "isPending" in u ? !!u.isPending : false;
};
const isEmptyFlag = () => {
const u = getState();
return "isEmpty" in u ? !!u.isEmpty : false;
};
const hasFlush = typeof utilInstance.flush === "function";
const hasCancel = typeof utilInstance.cancel === "function";
const hasReset = typeof utilInstance.reset === "function";
const hasClear = typeof utilInstance.clear === "function";
const hasStart = typeof utilInstance.start === "function";
const hasStop = typeof utilInstance.stop === "function";
const hasStartStop = hasStart && hasStop;
const isRunning = () => {
const u = getState();
return "isRunning" in u ? !!u.isRunning : true;
};
if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false));
`${props.utilName}`;
const utilNameLower = props.utilName.toLowerCase();
return ssr(_tmpl$2$2, ssrAttribute("class", escape(styles().actionsRow, true), false), hasPending() && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotBlue, true), false), isPending() ? "Restore Pending" : "Trigger Pending"), hasFlush && ssr(_tmpl$4, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotRed, true), false)), hasReset && ssr(_tmpl$6, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotYellow, true), false)), hasClear && ssr(_tmpl$7, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotOrange, true), false)), hasStartStop && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotPurple, true), false), isRunning() ? "Stop" : "Start"));
}
//#endregion
//#region src/components/StateHeader.tsx
var _tmpl$$2 = [
"<div",
"><div",
">",
"</div><div style=\"",
"\"><div",
"><div",
">Key</div><div",
">",
"</div><div",
">Last Updated</div><div",
">",
" (",
")</div></div><div",
" style=\"",
"\">",
"% reduction</div></div></div>"
];
dayjs.extend(relativeTime);
function reductionFromState(entry, state) {
if (!state) return 0;
const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0;
if (entry.type.toLowerCase().includes("batcher")) {
const totalItemsProcessed = Number(state.totalItemsProcessed) || 0;
if (totalItemsProcessed === 0) return 0;
return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
}
let requestCount = 0;
if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0;
else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0;
else return 0;
if (requestCount === 0) return 0;
const reduction = requestCount - completedExecutions;
return Math.round(reduction / requestCount * 100);
}
function StateHeaderInner(props) {
const key = props.entry.instance.key;
const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now();
const getRelativeTime = () => {
const at = updatedAt();
const diffMs = props.now() - at;
const diffSeconds = Math.floor(diffMs / 1e3);
if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
return dayjs(at).fromNow();
};
const store = props.entry.instance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(props.entry.instance);
const reductionPercentage = () => reductionFromState(props.entry, stateAccessor());
return ssr(_tmpl$$2, ssrAttribute("class", escape(props.styles.stateHeader, true), false), ssrAttribute("class", escape(props.styles.stateTitle, true), false), escape(props.entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(props.styles.infoGrid, true), false), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(key), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(new Date(updatedAt()).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage()));
}
function StateHeader(props) {
const styles = useStyles();
const [now, setNow] = createSignal(Date.now());
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now());
}, 1e3);
onCleanup(() => {
clearInterval(interval);
});
});
return createComponent(Show, {
get when() {
return props.selectedInstance();
},
children: (entry) => createComponent(StateHeaderInner, {
get entry() {
return entry();
},
get styles() {
return styles();
},
now,
get utilState() {
return props.utilState;
}
})
});
}
//#endregion
//#region src/components/UtilStateJsonTree.tsx
/**
* Subscribes to the util's TanStack Store so the tree updates live (same
* idea as {@link UtilList} status). A plain snapshot would only refresh when the
* devtools shell re-renders, which does not happen on nested store updates.
*/
function UtilStateJsonTree(props) {
const store = props.instance?.store;
if (isPacerUtilTanStackStore(store)) {
const snapshot = useSelector(store, (s) => s);
return createComponent(JsonTree, { get value() {
return snapshot();
} });
}
return createComponent(JsonTree, { get value() {
return getPacerUtilStoreState(props.instance);
} });
}
//#endregion
//#region src/components/DetailsPanel.tsx
var _tmpl$$1 = [
"<div",
">",
"</div>"
];
var _tmpl$2$1 = ["<div", ">Select a util from the left panel to view its state</div>"];
var _tmpl$3 = [
"<div",
"><div",
"><div",
">Actions</div>",
"</div><div",
"><div",
">State</div><div",
">",
"</div></div><div",
"><div",
">Options</div><div",
">",
"</div></div></div>"
];
function DetailsPanel(props) {
const styles = useStyles();
return ssr(_tmpl$$1, ssrAttribute("class", escape(styles().stateDetails, true), false), (() => {
const entry = props.selectedInstance();
if (!entry) return ssr(_tmpl$2$1, ssrAttribute("class", escape(styles().noSelection, true), false));
return escape([createComponent(StateHeader, {
get selectedInstance() {
return props.selectedInstance;
},
get utilState() {
return props.utilState;
}
}), ssr(_tmpl$3, ssrAttribute("class", escape(styles().detailsGrid, true), false), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), escape(createComponent(ActionButtons, {
get instance() {
return entry.instance;
},
get utilName() {
return entry.type;
}
})), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(UtilStateJsonTree, { get instance() {
return entry.instance;
} })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() {
return entry.instance.options;
} })))]);
})());
}
//#endregion
//#region src/components/Shell.tsx
var _tmpl$ = [
"<div",
"><div",
" style=\"",
"\">",
"</div><div class=\"",
"\"></div><div",
" style=\"",
"\"><div",
">Details</div>",
"</div></div>"
];
var _tmpl$2 = [
"<div",
" style=\"",
"\">",
"</div>"
];
function readResizeObserverBlockSize(entry) {
const [box] = entry.borderBoxSize;
if (box !== void 0) return box.blockSize;
return entry.contentRect.height;
}
/** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
function resolvePluginHost(el) {
const byId = el.closest("[id^=\"plugin-container-\"]");
if (byId) return byId;
return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement;
}
function Shell() {
const styles = useStyles();
const state = usePacerDevtoolsState();
const utilState = () => state;
const [selectedKey, setSelectedKey] = createSignal(null);
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300);
const [isDragging, setIsDragging] = createSignal(false);
const [shellRootEl, setShellRootEl] = createSignal(null);
const [slotHeightPx, setSlotHeightPx] = createSignal(void 0);
const selectedInstance = createMemo(() => {
const key = selectedKey();
if (!key) return null;
for (const group of UTIL_GROUPS) {
const instance = utilState()[group.key].find((inst) => inst.key === key);
if (instance) return {
instance,
type: group.displayName
};
}
return null;
});
let dragStartX = 0;
let dragStartWidth = 0;
const handleMouseMove = (e) => {
if (!isDragging()) return;
e.preventDefault();
const deltaX = e.clientX - dragStartX;
const newWidth = Math.max(150, Math.min(800, dragStartWidth + deltaX));
setLeftPanelWidth(newWidth);
};
const handleMouseUp = () => {
setIsDragging(false);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
onMount(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
});
onCleanup(() => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
});
/**
* TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
* ancestors, so percentage height often never constrains our tree. Observing the
* outer `plugin-container-*` host (not the immediate React mount wrapper) and
* setting an explicit pixel height makes inner overflow-y regions scroll without
* capping to the mount div’s collapsed height.
*/
createEffect(() => {
const el = shellRootEl();
if (!el || typeof ResizeObserver === "undefined") return;
const pluginSlot = resolvePluginHost(el);
if (!pluginSlot) return;
const ro = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) return;
const target = entry.target;
const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry);
if (h > 0) setSlotHeightPx(Math.round(h));
});
ro.observe(pluginSlot);
onCleanup(() => ro.disconnect());
});
return ssr(_tmpl$2, ssrAttribute("class", escape(styles().shellRoot, true), false), ssrStyle({ ...slotHeightPx() !== void 0 ? {
height: `${slotHeightPx()}px`,
"max-height": `${slotHeightPx()}px`
} : {} }), escape(createComponent(MainPanel, {
get ["class"]() {
return styles().mainPanelShell;
},
get children() {
return [createComponent(Header, { get children() {
return createComponent(HeaderLogo, {
flavor: {
light: "#84cc16",
dark: "#84cc16"
},
children: "TanStack Pacer"
});
} }), ssr(_tmpl$, ssrAttribute("class", escape(styles().mainContainer, true), false), ssrAttribute("class", escape(styles().leftPanel, true), false), ssrStyleProperty("width:", `${escape(leftPanelWidth(), true)}px`) + ssrStyleProperty(";min-width:", "150px") + ssrStyleProperty(";max-width:", "800px"), escape(createComponent(UtilList, {
selectedKey,
setSelectedKey,
utilState
})), `${escape(styles().dragHandle, true)} ${isDragging() ? "dragging" : ""}`, ssrAttribute("class", escape(styles().rightPanel, true), false), ssrStyleProperty("flex:", 1), ssrAttribute("class", escape(styles().panelHeader, true), false), escape(createComponent(DetailsPanel, {
selectedInstance,
utilState
})))];
}
})));
}
//#endregion
//#region src/components/index.tsx
function PacerDevtools(props) {
return createComponent(ThemeContextProvider, {
get theme() {
return props.theme;
},
get children() {
return createComponent(PacerContextProvider, { get children() {
return createComponent(Shell, {});
} });
}
});
}
//#endregion
export { PacerDevtools as default };
+0
-1
import { ClassType } from "@tanstack/devtools-utils/solid";
//#region src/core.d.ts

@@ -4,0 +3,0 @@ interface PacerDevtoolsInit {}

"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const coreClasses = constructCoreClass(() => import("./components-hqYwCNDx.js"));
const coreClasses = constructCoreClass(() => import("./components-BwHXH_1Y.js"));
const PacerDevtoolsCore$1 = coreClasses[0];

@@ -6,0 +6,0 @@ const PacerDevtoolsCoreNoOp = coreClasses[1];

import { ClassType } from "@tanstack/devtools-utils/solid";
//#region src/core.d.ts

@@ -4,0 +3,0 @@ interface PacerDevtoolsInit {}

"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const coreClasses = constructCoreClass(() => import("../components-hqYwCNDx.js"));
const coreClasses = constructCoreClass(() => import("../components-BwHXH_1Y.js"));
const PacerDevtoolsCore = coreClasses[0];

@@ -6,0 +6,0 @@ coreClasses[1];

"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const coreClasses = constructCoreClass(() => import("../components-DZv7GBzY.js"));
const coreClasses = constructCoreClass(() => import("../components-BXAd-LGS.js"));
const PacerDevtoolsCore = coreClasses[0];

@@ -6,0 +6,0 @@ coreClasses[1];

"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const coreClasses = constructCoreClass(() => import("./components-DZv7GBzY.js"));
const coreClasses = constructCoreClass(() => import("./components-BXAd-LGS.js"));
const PacerDevtoolsCore$1 = coreClasses[0];

@@ -6,0 +6,0 @@ const PacerDevtoolsCoreNoOp = coreClasses[1];

{
"name": "@tanstack/pacer-devtools",
"version": "1.3.1",
"version": "1.4.0",
"description": "Devtools for Pacer.",

@@ -74,15 +74,15 @@ "author": "Tanner Linsley",

"dependencies": {
"@tanstack/devtools-ui": "0.5.2",
"@tanstack/devtools-utils": "^0.5.0",
"@tanstack/solid-store": "^0.11.0",
"@tanstack/devtools-ui": "0.6.0",
"@tanstack/devtools-utils": "^0.6.0",
"@tanstack/solid-store": "^0.11.1",
"clsx": "^2.1.1",
"csstype": "^3.2.3",
"dayjs": "^1.11.20",
"dayjs": "^1.11.21",
"goober": "^2.1.19",
"solid-js": "^1.9.12"
"solid-js": "^1.9.14"
},
"devDependencies": {
"rolldown-plugin-solid": "^0.2.1",
"tsdown": "^0.22.0",
"vite-plugin-solid": "^2.11.12"
"tsdown": "^0.22.14",
"vite-plugin-solid": "^2.11.14"
},

@@ -89,0 +89,0 @@ "scripts": {

<div align="center">
<img src="./media/header_pacer.png" >
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="https://tanstack.com/api/readme/pacer.png?theme=dark"
/>
<source
media="(prefers-color-scheme: light)"
srcset="https://tanstack.com/api/readme/pacer.png"
/>
<img
src="https://tanstack.com/api/readme/pacer.png"
alt="TanStack Pacer"
width="900"
/>
</picture>
</div>

@@ -32,4 +46,5 @@

<div align="center">
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

@@ -36,0 +51,0 @@

import * as goober from 'goober'
import { createEffect, createSignal } from 'solid-js'
import { useTheme } from '@tanstack/devtools-ui'
import { createTheme } from '@tanstack/devtools-ui'
import { tokens } from './tokens'

@@ -379,3 +379,3 @@

export function useStyles() {
const { theme } = useTheme()
const { theme } = createTheme()
const [styles, setStyles] = createSignal(stylesFactory(theme()))

@@ -382,0 +382,0 @@ createEffect(() => {

import { createComponent, escape, ssr, ssrAttribute, ssrStyle, ssrStyleProperty } from "solid-js/web";
import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui";
import { createStore, produce } from "solid-js/store";
import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client";
import * as goober from "goober";
import { shallow, useSelector } from "@tanstack/solid-store";
import clsx from "clsx";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
//#region src/PacerContextProvider.tsx
const initialPacerDevtoolsStore = {
asyncBatchers: [],
asyncDebouncers: [],
asyncQueuers: [],
asyncRateLimiters: [],
asyncThrottlers: [],
batchers: [],
debouncers: [],
queuers: [],
rateLimiters: [],
throttlers: [],
lastUpdatedByKey: {}
};
const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]);
/**
* Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per
* event suffix so the same `pacer:${suffix}` channel the library emits on is
* observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is
* not always relayed the same way by the shell.
*
* `d-*` suffixes are used when the devtools panel pushes state back (see
* ActionButtons); those must update the panel store too.
*/
const PACER_DEVTOOLS_UTIL_EVENTS = [
{
listKey: "asyncBatchers",
suffixes: ["AsyncBatcher", "d-AsyncBatcher"]
},
{
listKey: "asyncDebouncers",
suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"]
},
{
listKey: "asyncQueuers",
suffixes: ["AsyncQueuer", "d-AsyncQueuer"]
},
{
listKey: "asyncRateLimiters",
suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"]
},
{
listKey: "asyncThrottlers",
suffixes: ["AsyncThrottler", "d-AsyncThrottler"]
},
{
listKey: "batchers",
suffixes: ["Batcher", "d-Batcher"]
},
{
listKey: "debouncers",
suffixes: ["Debouncer", "d-Debouncer"]
},
{
listKey: "queuers",
suffixes: ["Queuer", "d-Queuer"]
},
{
listKey: "rateLimiters",
suffixes: ["RateLimiter", "d-RateLimiter"]
},
{
listKey: "throttlers",
suffixes: ["Throttler", "d-Throttler"]
}
];
function PacerContextProvider(props) {
const [store, setStore] = createStore(initialPacerDevtoolsStore);
createEffect(() => {
const cleanups = [];
for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => {
const key = e.payload.key;
if (!key) return;
const instance = getPacerDevtoolsInstance(key);
if (!instance || typeof instance !== "object") return;
setStore(produce((draft) => {
const list = draft[listKey];
const index = list.findIndex((item) => item.key === key);
const inst = instance;
if (index !== -1) list[index] = inst;
else list.push(inst);
draft.lastUpdatedByKey[key] = Date.now();
}));
}));
onCleanup(() => {
for (const cleanup of cleanups) cleanup();
});
});
return createComponent(PacerDevtoolsContext.Provider, {
value: [store, setStore],
get children() {
return props.children;
}
});
}
const usePacerDevtoolsContext = () => {
return useContext(PacerDevtoolsContext);
};
const usePacerDevtoolsState = () => {
const [state] = usePacerDevtoolsContext();
return state;
};
//#endregion
//#region src/styles/tokens.ts
const tokens = {
colors: {
inherit: "inherit",
current: "currentColor",
transparent: "transparent",
black: "#000000",
white: "#ffffff",
neutral: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
darkGray: {
50: "#525c7a",
100: "#49536e",
200: "#414962",
300: "#394056",
400: "#313749",
500: "#292e3d",
600: "#212530",
700: "#191c24",
800: "#111318",
900: "#0b0d10"
},
gray: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
blue: {
25: "#F5FAFF",
50: "#EFF8FF",
100: "#D1E9FF",
200: "#B2DDFF",
300: "#84CAFF",
400: "#53B1FD",
500: "#2E90FA",
600: "#1570EF",
700: "#175CD3",
800: "#1849A9",
900: "#194185"
},
green: {
25: "#F6FEF9",
50: "#ECFDF3",
100: "#D1FADF",
200: "#A6F4C5",
300: "#6CE9A6",
400: "#32D583",
500: "#12B76A",
600: "#039855",
700: "#027A48",
800: "#05603A",
900: "#054F31"
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a"
},
yellow: {
25: "#FFFCF5",
50: "#FFFAEB",
100: "#FEF0C7",
200: "#FEDF89",
300: "#FEC84B",
400: "#FDB022",
500: "#F79009",
600: "#DC6803",
700: "#B54708",
800: "#93370D",
900: "#7A2E0E"
},
purple: {
25: "#FAFAFF",
50: "#F4F3FF",
100: "#EBE9FE",
200: "#D9D6FE",
300: "#BDB4FE",
400: "#9B8AFB",
500: "#7A5AF8",
600: "#6938EF",
700: "#5925DC",
800: "#4A1FB8",
900: "#3E1C96"
},
teal: {
25: "#F6FEFC",
50: "#F0FDF9",
100: "#CCFBEF",
200: "#99F6E0",
300: "#5FE9D0",
400: "#2ED3B7",
500: "#15B79E",
600: "#0E9384",
700: "#107569",
800: "#125D56",
900: "#134E48"
},
pink: {
25: "#fdf2f8",
50: "#fce7f3",
100: "#fbcfe8",
200: "#f9a8d4",
300: "#f472b6",
400: "#ec4899",
500: "#db2777",
600: "#be185d",
700: "#9d174d",
800: "#831843",
900: "#500724"
},
cyan: {
25: "#ecfeff",
50: "#cffafe",
100: "#a5f3fc",
200: "#67e8f9",
300: "#22d3ee",
400: "#06b6d4",
500: "#0891b2",
600: "#0e7490",
700: "#155e75",
800: "#164e63",
900: "#083344"
}
},
alpha: {
100: "ff",
90: "e5",
80: "cc",
70: "b3",
60: "99",
50: "80",
40: "66",
30: "4d",
20: "33",
10: "1a",
0: "00"
},
font: {
size: {
"2xs": "calc(var(--tsrd-font-size) * 0.625)",
xs: "calc(var(--tsrd-font-size) * 0.75)",
sm: "calc(var(--tsrd-font-size) * 0.875)",
md: "var(--tsrd-font-size)",
lg: "calc(var(--tsrd-font-size) * 1.125)",
xl: "calc(var(--tsrd-font-size) * 1.25)",
"2xl": "calc(var(--tsrd-font-size) * 1.5)",
"3xl": "calc(var(--tsrd-font-size) * 1.875)",
"4xl": "calc(var(--tsrd-font-size) * 2.25)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.75)",
"7xl": "calc(var(--tsrd-font-size) * 4.5)",
"8xl": "calc(var(--tsrd-font-size) * 6)",
"9xl": "calc(var(--tsrd-font-size) * 8)"
},
lineHeight: {
"3xs": "calc(var(--tsrd-font-size) * 0.75)",
"2xs": "calc(var(--tsrd-font-size) * 0.875)",
xs: "calc(var(--tsrd-font-size) * 1)",
sm: "calc(var(--tsrd-font-size) * 1.25)",
md: "calc(var(--tsrd-font-size) * 1.5)",
lg: "calc(var(--tsrd-font-size) * 1.75)",
xl: "calc(var(--tsrd-font-size) * 2)",
"2xl": "calc(var(--tsrd-font-size) * 2.25)",
"3xl": "calc(var(--tsrd-font-size) * 2.5)",
"4xl": "calc(var(--tsrd-font-size) * 2.75)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.25)",
"7xl": "calc(var(--tsrd-font-size) * 3.5)",
"8xl": "calc(var(--tsrd-font-size) * 3.75)",
"9xl": "calc(var(--tsrd-font-size) * 4)"
},
weight: {
thin: "100",
extralight: "200",
light: "300",
normal: "400",
medium: "500",
semibold: "600",
bold: "700",
extrabold: "800",
black: "900"
},
fontFamily: {
sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif",
mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace`
}
},
breakpoints: {
xs: "320px",
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px"
},
border: { radius: {
none: "0px",
xs: "calc(var(--tsrd-font-size) * 0.125)",
sm: "calc(var(--tsrd-font-size) * 0.25)",
md: "calc(var(--tsrd-font-size) * 0.375)",
lg: "calc(var(--tsrd-font-size) * 0.5)",
xl: "calc(var(--tsrd-font-size) * 0.75)",
"2xl": "calc(var(--tsrd-font-size) * 1)",
"3xl": "calc(var(--tsrd-font-size) * 1.5)",
full: "9999px"
} },
size: {
0: "0px",
.25: "calc(var(--tsrd-font-size) * 0.0625)",
.5: "calc(var(--tsrd-font-size) * 0.125)",
1: "calc(var(--tsrd-font-size) * 0.25)",
1.5: "calc(var(--tsrd-font-size) * 0.375)",
2: "calc(var(--tsrd-font-size) * 0.5)",
2.5: "calc(var(--tsrd-font-size) * 0.625)",
3: "calc(var(--tsrd-font-size) * 0.75)",
3.5: "calc(var(--tsrd-font-size) * 0.875)",
4: "calc(var(--tsrd-font-size) * 1)",
4.5: "calc(var(--tsrd-font-size) * 1.125)",
5: "calc(var(--tsrd-font-size) * 1.25)",
5.5: "calc(var(--tsrd-font-size) * 1.375)",
6: "calc(var(--tsrd-font-size) * 1.5)",
6.5: "calc(var(--tsrd-font-size) * 1.625)",
7: "calc(var(--tsrd-font-size) * 1.75)",
8: "calc(var(--tsrd-font-size) * 2)",
9: "calc(var(--tsrd-font-size) * 2.25)",
10: "calc(var(--tsrd-font-size) * 2.5)",
11: "calc(var(--tsrd-font-size) * 2.75)",
12: "calc(var(--tsrd-font-size) * 3)",
14: "calc(var(--tsrd-font-size) * 3.5)",
16: "calc(var(--tsrd-font-size) * 4)",
20: "calc(var(--tsrd-font-size) * 5)",
24: "calc(var(--tsrd-font-size) * 6)",
28: "calc(var(--tsrd-font-size) * 7)",
32: "calc(var(--tsrd-font-size) * 8)",
36: "calc(var(--tsrd-font-size) * 9)",
40: "calc(var(--tsrd-font-size) * 10)",
44: "calc(var(--tsrd-font-size) * 11)",
48: "calc(var(--tsrd-font-size) * 12)",
52: "calc(var(--tsrd-font-size) * 13)",
56: "calc(var(--tsrd-font-size) * 14)",
60: "calc(var(--tsrd-font-size) * 15)",
64: "calc(var(--tsrd-font-size) * 16)",
72: "calc(var(--tsrd-font-size) * 18)",
80: "calc(var(--tsrd-font-size) * 20)",
96: "calc(var(--tsrd-font-size) * 24)"
},
shadow: {
xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`,
sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`,
md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`,
lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`,
xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`,
"2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`,
inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`,
none: () => `none`
},
zIndices: {
hide: -1,
auto: "auto",
base: 0,
docked: 10,
dropdown: 1e3,
sticky: 1100,
banner: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800
}
};
//#endregion
//#region src/styles/use-styles.ts
const stylesFactory = (theme) => {
const { colors, font, size, alpha, border } = tokens;
const { fontFamily, size: fontSize } = font;
const css = goober.css;
const t = (light, dark) => theme === "light" ? light : dark;
return {
/**
* Fills the devtools-utils portal div (height: 100%) and is a flex parent so MainPanel
* gets a real flex height budget (not just height: 100% without a flex chain).
*/
shellRoot: css`
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
`,
/** Overrides devtools-ui MainPanel overflow-y: auto so inner columns own scrolling */
mainPanelShell: css`
display: flex;
flex-direction: column;
flex: 1 1 0%;
min-height: 0;
overflow: hidden !important;
`,
mainContainer: css`
display: flex;
flex: 1 1 0%;
min-height: 0;
overflow: hidden;
padding: ${size[2]};
padding-top: 0;
margin-top: ${size[2]};
`,
dragHandle: css`
width: 8px;
background: ${t(colors.gray[300], colors.darkGray[600])};
cursor: col-resize;
position: relative;
transition: all 0.2s ease;
user-select: none;
pointer-events: all;
margin: 0 ${size[1]};
border-radius: 2px;
&:hover {
background: ${t(colors.blue[600], colors.blue[500])};
margin: 0 ${size[1]};
}
&.dragging {
background: ${t(colors.blue[700], colors.blue[600])};
margin: 0 ${size[1]};
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background: ${t(colors.gray[400], colors.darkGray[400])};
border-radius: 1px;
pointer-events: none;
}
&:hover::after,
&.dragging::after {
background: ${t(colors.blue[500], colors.blue[300])};
}
`,
leftPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex-shrink: 0;
`,
rightPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex: 1;
`,
panelHeader: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
padding: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
background: ${t(colors.gray[100], colors.darkGray[800])};
flex-shrink: 0;
`,
utilList: css`
flex: 1;
overflow-y: auto;
padding: ${size[1]};
min-height: 0;
`,
utilGroup: css`
margin-bottom: ${size[2]};
`,
utilGroupHeader: css`
font-size: ${fontSize.xs};
font-weight: ${font.weight.semibold};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: ${size[1]};
padding: ${size[1]} ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
`,
utilRow: css`
display: flex;
justify-content: space-between;
align-items: center;
padding: ${size[2]};
margin-bottom: ${size[1]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[600])};
border-color: ${t(colors.gray[400], colors.darkGray[500])};
}
`,
utilRowSelected: css`
background: ${t(colors.blue[100], colors.blue[900] + alpha[20])};
border-color: ${t(colors.blue[600], colors.blue[500])};
box-shadow: 0 0 0 1px
${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])};
`,
utilKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
utilStatus: css`
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
padding: ${size[1]} ${size[1]};
background: ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.sm};
margin-left: ${size[1]};
`,
stateDetails: css`
flex: 1;
overflow-y: auto;
padding: ${size[2]};
min-height: 0;
`,
stateHeader: css`
margin-bottom: ${size[2]};
padding-bottom: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
`,
stateTitle: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
margin-bottom: ${size[1]};
`,
stateKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
word-break: break-all;
`,
stateContent: css`
background: ${t(colors.gray[100], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[2]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
detailsGrid: css`
display: grid;
grid-template-columns: 1fr;
gap: ${size[2]};
align-items: start;
`,
detailSection: css`
background: ${t(colors.white, colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.md};
padding: ${size[2]};
`,
detailSectionHeader: css`
font-size: ${fontSize.sm};
font-weight: ${font.weight.bold};
color: ${t(colors.gray[800], colors.gray[200])};
margin-bottom: ${size[1]};
text-transform: uppercase;
letter-spacing: 0.04em;
`,
actionsRow: css`
display: flex;
flex-wrap: wrap;
gap: ${size[2]};
`,
actionButton: css`
display: inline-flex;
align-items: center;
gap: ${size[1]};
padding: ${size[1]} ${size[2]};
border-radius: ${border.radius.md};
border: 1px solid ${t(colors.gray[300], colors.darkGray[500])};
background: ${t(colors.gray[200], colors.darkGray[600])};
color: ${t(colors.gray[900], colors.gray[100])};
font-size: ${fontSize.xs};
cursor: pointer;
user-select: none;
transition:
background 0.15s,
border-color 0.15s;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[500])};
border-color: ${t(colors.gray[400], colors.darkGray[400])};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background: ${t(colors.gray[200], colors.darkGray[600])};
border-color: ${t(colors.gray[300], colors.darkGray[500])};
}
}
`,
actionDotBlue: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.blue[400]};
`,
actionDotGreen: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.green[400]};
`,
actionDotRed: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.red[400]};
`,
actionDotYellow: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.yellow[400]};
`,
actionDotOrange: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.pink[400]};
`,
actionDotPurple: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.purple[400]};
`,
infoGrid: css`
display: grid;
grid-template-columns: auto 1fr;
gap: ${size[1]};
row-gap: ${size[1]};
align-items: center;
`,
infoLabel: css`
color: ${t(colors.gray[600], colors.gray[400])};
font-size: ${fontSize.xs};
text-transform: uppercase;
letter-spacing: 0.05em;
`,
infoValueMono: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
word-break: break-all;
`,
noSelection: css`
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: ${t(colors.gray[500], colors.gray[500])};
font-style: italic;
text-align: center;
padding: ${size[4]};
`,
sectionContainer: css`
display: flex;
flex-wrap: wrap;
gap: ${size[4]};
`,
section: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))};
padding: ${size[4]};
margin-bottom: ${size[4]};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
min-width: 0;
max-width: 33%;
max-height: fit-content;
`,
sectionHeader: css`
font-size: ${fontSize.lg};
font-weight: ${font.weight.bold};
margin-bottom: ${size[2]};
color: ${t(colors.blue[600], colors.blue[400])};
letter-spacing: 0.01em;
display: flex;
align-items: center;
gap: ${size[2]};
`,
sectionEmpty: css`
color: ${t(colors.gray[500], colors.gray[500])};
font-size: ${fontSize.sm};
font-style: italic;
margin: ${size[2]} 0;
`,
instanceList: css`
display: flex;
flex-direction: column;
gap: ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
instanceCard: css`
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[3]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
font-size: ${fontSize.sm};
color: ${t(colors.gray[900], colors.gray[100])};
font-family: ${fontFamily.mono};
overflow-x: auto;
transition:
box-shadow 0.3s,
background 0.3s;
`
};
};
function useStyles() {
const { theme } = useTheme();
const [styles, setStyles] = createSignal(stylesFactory(theme()));
createEffect(() => {
setStyles(stylesFactory(theme()));
});
return styles;
}
//#endregion
//#region src/components/util-groups.ts
const UTIL_GROUPS = [
{
key: "debouncers",
label: "Debouncers",
displayName: "Debouncer"
},
{
key: "throttlers",
label: "Throttlers",
displayName: "Throttler"
},
{
key: "batchers",
label: "Batchers",
displayName: "Batcher"
},
{
key: "queuers",
label: "Queuers",
displayName: "Queuer"
},
{
key: "rateLimiters",
label: "Rate Limiters",
displayName: "Rate Limiter"
},
{
key: "asyncDebouncers",
label: "Async Debouncers",
displayName: "Async Debouncer"
},
{
key: "asyncThrottlers",
label: "Async Throttlers",
displayName: "Async Throttler"
},
{
key: "asyncBatchers",
label: "Async Batchers",
displayName: "Async Batcher"
},
{
key: "asyncQueuers",
label: "Async Queuers",
displayName: "Async Queuer"
},
{
key: "asyncRateLimiters",
label: "Async Rate Limiters",
displayName: "Async Rate Limiter"
}
];
//#endregion
//#region src/components/UtilList.tsx
var _tmpl$$4 = [
"<div",
">",
"</div>"
], _tmpl$2$3 = [
"<div",
"><div",
">",
"</div>",
"</div>"
], _tmpl$3$2 = [
"<div",
"><div",
">",
"</div><div",
">",
"</div></div>"
];
function UtilList(props) {
const styles = useStyles();
return ssr(_tmpl$$4, ssrAttribute("class", escape(styles().utilList, true), false), escape(createComponent(For, {
each: UTIL_GROUPS,
children: (group) => props.utilState()[group.key].length > 0 && ssr(_tmpl$2$3, ssrAttribute("class", escape(styles().utilGroup, true), false), ssrAttribute("class", escape(styles().utilGroupHeader, true), false), escape(group.label), escape(createComponent(For, {
get each() {
return props.utilState()[group.key];
},
children: (instance) => {
const status = (() => {
try {
const statusAccessor = useSelector(instance.store, (s) => s.status);
return () => statusAccessor() ?? "unknown";
} catch {
return () => "unknown";
}
})();
return ssr(_tmpl$3$2, ssrAttribute("class", escape(clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), true), false), ssrAttribute("class", escape(styles().utilKey, true), false), escape(instance.key), ssrAttribute("class", escape(styles().utilStatus, true), false), escape(status()));
}
})))
})));
}
//#endregion
//#region src/utils/read-pacer-store-state.ts
/** TanStack Store (`@tanstack/store`) used by pacer utils. */
function isPacerUtilTanStackStore(x) {
return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function";
}
/**
* Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may
* also expose a reactive `state` field. Use this for devtools UI that needs a
* plain snapshot (e.g. JsonTree).
*/
function getPacerUtilStoreState(instance) {
const store = instance.store;
if (store && typeof store.get === "function") return store.get();
if (store && store.state !== void 0) return store.state;
return instance.state;
}
//#endregion
//#region src/components/ActionButtons.tsx
var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$2 = [
"<div",
">",
"",
"",
"",
"",
"",
"</div>"
], _tmpl$3$1 = [
"<button",
"><span",
"></span>",
"</button>"
], _tmpl$4 = [
"<button",
"",
"><span",
"></span>Flush</button>"
], _tmpl$5 = [
"<button",
"",
"><span",
"></span>Cancel</button>"
], _tmpl$6 = [
"<button",
"><span",
"></span>Reset</button>"
], _tmpl$7 = [
"<button",
"",
"><span",
"></span>Clear</button>"
];
function ActionButtons(props) {
const styles = useStyles();
const utilInstance = props.instance;
const store = utilInstance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(utilInstance);
const getState = () => stateAccessor();
const hasPending = () => {
return "isPending" in getState();
};
const isPending = () => {
const u = getState();
return "isPending" in u ? !!u.isPending : false;
};
const isEmptyFlag = () => {
const u = getState();
return "isEmpty" in u ? !!u.isEmpty : false;
};
const hasFlush = typeof utilInstance.flush === "function";
const hasCancel = typeof utilInstance.cancel === "function";
const hasReset = typeof utilInstance.reset === "function";
const hasClear = typeof utilInstance.clear === "function";
const hasStart = typeof utilInstance.start === "function";
const hasStop = typeof utilInstance.stop === "function";
const hasStartStop = hasStart && hasStop;
const isRunning = () => {
const u = getState();
return "isRunning" in u ? !!u.isRunning : true;
};
if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false));
`${props.utilName}`;
const utilNameLower = props.utilName.toLowerCase();
return ssr(_tmpl$2$2, ssrAttribute("class", escape(styles().actionsRow, true), false), hasPending() && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotBlue, true), false), isPending() ? "Restore Pending" : "Trigger Pending"), hasFlush && ssr(_tmpl$4, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotRed, true), false)), hasReset && ssr(_tmpl$6, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotYellow, true), false)), hasClear && ssr(_tmpl$7, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotOrange, true), false)), hasStartStop && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotPurple, true), false), isRunning() ? "Stop" : "Start"));
}
//#endregion
//#region src/components/StateHeader.tsx
var _tmpl$$2 = [
"<div",
"><div",
">",
"</div><div style=\"",
"\"><div",
"><div",
">Key</div><div",
">",
"</div><div",
">Last Updated</div><div",
">",
" (",
")</div></div><div",
" style=\"",
"\">",
"% reduction</div></div></div>"
];
dayjs.extend(relativeTime);
function reductionFromState(entry, state) {
if (!state) return 0;
const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0;
if (entry.type.toLowerCase().includes("batcher")) {
const totalItemsProcessed = Number(state.totalItemsProcessed) || 0;
if (totalItemsProcessed === 0) return 0;
return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
}
let requestCount = 0;
if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0;
else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0;
else return 0;
if (requestCount === 0) return 0;
const reduction = requestCount - completedExecutions;
return Math.round(reduction / requestCount * 100);
}
function StateHeaderInner(props) {
const key = props.entry.instance.key;
const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now();
const getRelativeTime = () => {
const at = updatedAt();
const diffMs = props.now() - at;
const diffSeconds = Math.floor(diffMs / 1e3);
if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
return dayjs(at).fromNow();
};
const store = props.entry.instance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(props.entry.instance);
const reductionPercentage = () => reductionFromState(props.entry, stateAccessor());
return ssr(_tmpl$$2, ssrAttribute("class", escape(props.styles.stateHeader, true), false), ssrAttribute("class", escape(props.styles.stateTitle, true), false), escape(props.entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(props.styles.infoGrid, true), false), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(key), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(new Date(updatedAt()).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage()));
}
function StateHeader(props) {
const styles = useStyles();
const [now, setNow] = createSignal(Date.now());
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now());
}, 1e3);
onCleanup(() => {
clearInterval(interval);
});
});
return createComponent(Show, {
get when() {
return props.selectedInstance();
},
children: (entry) => createComponent(StateHeaderInner, {
get entry() {
return entry();
},
get styles() {
return styles();
},
now,
get utilState() {
return props.utilState;
}
})
});
}
//#endregion
//#region src/components/UtilStateJsonTree.tsx
/**
* Subscribes to the util's TanStack Store so the tree updates live (same
* idea as {@link UtilList} status). A plain snapshot would only refresh when the
* devtools shell re-renders, which does not happen on nested store updates.
*/
function UtilStateJsonTree(props) {
const store = props.instance?.store;
if (isPacerUtilTanStackStore(store)) {
const snapshot = useSelector(store, (s) => s);
return createComponent(JsonTree, { get value() {
return snapshot();
} });
}
return createComponent(JsonTree, { get value() {
return getPacerUtilStoreState(props.instance);
} });
}
//#endregion
//#region src/components/DetailsPanel.tsx
var _tmpl$$1 = [
"<div",
">",
"</div>"
], _tmpl$2$1 = ["<div", ">Select a util from the left panel to view its state</div>"], _tmpl$3 = [
"<div",
"><div",
"><div",
">Actions</div>",
"</div><div",
"><div",
">State</div><div",
">",
"</div></div><div",
"><div",
">Options</div><div",
">",
"</div></div></div>"
];
function DetailsPanel(props) {
const styles = useStyles();
return ssr(_tmpl$$1, ssrAttribute("class", escape(styles().stateDetails, true), false), (() => {
const entry = props.selectedInstance();
if (!entry) return ssr(_tmpl$2$1, ssrAttribute("class", escape(styles().noSelection, true), false));
return escape([createComponent(StateHeader, {
get selectedInstance() {
return props.selectedInstance;
},
get utilState() {
return props.utilState;
}
}), ssr(_tmpl$3, ssrAttribute("class", escape(styles().detailsGrid, true), false), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), escape(createComponent(ActionButtons, {
get instance() {
return entry.instance;
},
get utilName() {
return entry.type;
}
})), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(UtilStateJsonTree, { get instance() {
return entry.instance;
} })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() {
return entry.instance.options;
} })))]);
})());
}
//#endregion
//#region src/components/Shell.tsx
var _tmpl$ = [
"<div",
"><div",
" style=\"",
"\">",
"</div><div class=\"",
"\"></div><div",
" style=\"",
"\"><div",
">Details</div>",
"</div></div>"
], _tmpl$2 = [
"<div",
" style=\"",
"\">",
"</div>"
];
function readResizeObserverBlockSize(entry) {
const [box] = entry.borderBoxSize;
if (box !== void 0) return box.blockSize;
return entry.contentRect.height;
}
/** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
function resolvePluginHost(el) {
const byId = el.closest("[id^=\"plugin-container-\"]");
if (byId) return byId;
return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement;
}
function Shell() {
const styles = useStyles();
const state = usePacerDevtoolsState();
const utilState = () => state;
const [selectedKey, setSelectedKey] = createSignal(null);
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300);
const [isDragging, setIsDragging] = createSignal(false);
const [shellRootEl, setShellRootEl] = createSignal(null);
const [slotHeightPx, setSlotHeightPx] = createSignal(void 0);
const selectedInstance = createMemo(() => {
const key = selectedKey();
if (!key) return null;
for (const group of UTIL_GROUPS) {
const instance = utilState()[group.key].find((inst) => inst.key === key);
if (instance) return {
instance,
type: group.displayName
};
}
return null;
});
let dragStartX = 0;
let dragStartWidth = 0;
const handleMouseMove = (e) => {
if (!isDragging()) return;
e.preventDefault();
const deltaX = e.clientX - dragStartX;
setLeftPanelWidth(Math.max(150, Math.min(800, dragStartWidth + deltaX)));
};
const handleMouseUp = () => {
setIsDragging(false);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
onMount(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
});
onCleanup(() => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
});
/**
* TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
* ancestors, so percentage height often never constrains our tree. Observing the
* outer `plugin-container-*` host (not the immediate React mount wrapper) and
* setting an explicit pixel height makes inner overflow-y regions scroll without
* capping to the mount div’s collapsed height.
*/
createEffect(() => {
const el = shellRootEl();
if (!el || typeof ResizeObserver === "undefined") return;
const pluginSlot = resolvePluginHost(el);
if (!pluginSlot) return;
const ro = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) return;
const target = entry.target;
const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry);
if (h > 0) setSlotHeightPx(Math.round(h));
});
ro.observe(pluginSlot);
onCleanup(() => ro.disconnect());
});
return ssr(_tmpl$2, ssrAttribute("class", escape(styles().shellRoot, true), false), ssrStyle({ ...slotHeightPx() !== void 0 ? {
height: `${slotHeightPx()}px`,
"max-height": `${slotHeightPx()}px`
} : {} }), escape(createComponent(MainPanel, {
get ["class"]() {
return styles().mainPanelShell;
},
get children() {
return [createComponent(Header, { get children() {
return createComponent(HeaderLogo, {
flavor: {
light: "#84cc16",
dark: "#84cc16"
},
children: "TanStack Pacer"
});
} }), ssr(_tmpl$, ssrAttribute("class", escape(styles().mainContainer, true), false), ssrAttribute("class", escape(styles().leftPanel, true), false), ssrStyleProperty("width:", `${escape(leftPanelWidth(), true)}px`) + ssrStyleProperty(";min-width:", "150px") + ssrStyleProperty(";max-width:", "800px"), escape(createComponent(UtilList, {
selectedKey,
setSelectedKey,
utilState
})), `${escape(styles().dragHandle, true)} ${isDragging() ? "dragging" : ""}`, ssrAttribute("class", escape(styles().rightPanel, true), false), ssrStyleProperty("flex:", 1), ssrAttribute("class", escape(styles().panelHeader, true), false), escape(createComponent(DetailsPanel, {
selectedInstance,
utilState
})))];
}
})));
}
//#endregion
//#region src/components/index.tsx
function PacerDevtools(props) {
return createComponent(ThemeContextProvider, {
get theme() {
return props.theme;
},
get children() {
return createComponent(PacerContextProvider, { get children() {
return createComponent(Shell, {});
} });
}
});
}
//#endregion
export { PacerDevtools as default };
import { className, createComponent, delegateEvents, effect, insert, memo, setStyleProperty, style, template, use } from "solid-js/web";
import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui";
import { createStore, produce } from "solid-js/store";
import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client";
import * as goober from "goober";
import { shallow, useSelector } from "@tanstack/solid-store";
import clsx from "clsx";
import { emitChange } from "@tanstack/pacer";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
//#region src/PacerContextProvider.tsx
const initialPacerDevtoolsStore = {
asyncBatchers: [],
asyncDebouncers: [],
asyncQueuers: [],
asyncRateLimiters: [],
asyncThrottlers: [],
batchers: [],
debouncers: [],
queuers: [],
rateLimiters: [],
throttlers: [],
lastUpdatedByKey: {}
};
const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]);
/**
* Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per
* event suffix so the same `pacer:${suffix}` channel the library emits on is
* observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is
* not always relayed the same way by the shell.
*
* `d-*` suffixes are used when the devtools panel pushes state back (see
* ActionButtons); those must update the panel store too.
*/
const PACER_DEVTOOLS_UTIL_EVENTS = [
{
listKey: "asyncBatchers",
suffixes: ["AsyncBatcher", "d-AsyncBatcher"]
},
{
listKey: "asyncDebouncers",
suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"]
},
{
listKey: "asyncQueuers",
suffixes: ["AsyncQueuer", "d-AsyncQueuer"]
},
{
listKey: "asyncRateLimiters",
suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"]
},
{
listKey: "asyncThrottlers",
suffixes: ["AsyncThrottler", "d-AsyncThrottler"]
},
{
listKey: "batchers",
suffixes: ["Batcher", "d-Batcher"]
},
{
listKey: "debouncers",
suffixes: ["Debouncer", "d-Debouncer"]
},
{
listKey: "queuers",
suffixes: ["Queuer", "d-Queuer"]
},
{
listKey: "rateLimiters",
suffixes: ["RateLimiter", "d-RateLimiter"]
},
{
listKey: "throttlers",
suffixes: ["Throttler", "d-Throttler"]
}
];
function PacerContextProvider(props) {
const [store, setStore] = createStore(initialPacerDevtoolsStore);
createEffect(() => {
const cleanups = [];
for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => {
const key = e.payload.key;
if (!key) return;
const instance = getPacerDevtoolsInstance(key);
if (!instance || typeof instance !== "object") return;
setStore(produce((draft) => {
const list = draft[listKey];
const index = list.findIndex((item) => item.key === key);
const inst = instance;
if (index !== -1) list[index] = inst;
else list.push(inst);
draft.lastUpdatedByKey[key] = Date.now();
}));
}));
onCleanup(() => {
for (const cleanup of cleanups) cleanup();
});
});
return createComponent(PacerDevtoolsContext.Provider, {
value: [store, setStore],
get children() {
return props.children;
}
});
}
const usePacerDevtoolsContext = () => {
return useContext(PacerDevtoolsContext);
};
const usePacerDevtoolsState = () => {
const [state] = usePacerDevtoolsContext();
return state;
};
//#endregion
//#region src/styles/tokens.ts
const tokens = {
colors: {
inherit: "inherit",
current: "currentColor",
transparent: "transparent",
black: "#000000",
white: "#ffffff",
neutral: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
darkGray: {
50: "#525c7a",
100: "#49536e",
200: "#414962",
300: "#394056",
400: "#313749",
500: "#292e3d",
600: "#212530",
700: "#191c24",
800: "#111318",
900: "#0b0d10"
},
gray: {
50: "#f9fafb",
100: "#f2f4f7",
200: "#eaecf0",
300: "#d0d5dd",
400: "#98a2b3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1d2939",
900: "#101828"
},
blue: {
25: "#F5FAFF",
50: "#EFF8FF",
100: "#D1E9FF",
200: "#B2DDFF",
300: "#84CAFF",
400: "#53B1FD",
500: "#2E90FA",
600: "#1570EF",
700: "#175CD3",
800: "#1849A9",
900: "#194185"
},
green: {
25: "#F6FEF9",
50: "#ECFDF3",
100: "#D1FADF",
200: "#A6F4C5",
300: "#6CE9A6",
400: "#32D583",
500: "#12B76A",
600: "#039855",
700: "#027A48",
800: "#05603A",
900: "#054F31"
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a"
},
yellow: {
25: "#FFFCF5",
50: "#FFFAEB",
100: "#FEF0C7",
200: "#FEDF89",
300: "#FEC84B",
400: "#FDB022",
500: "#F79009",
600: "#DC6803",
700: "#B54708",
800: "#93370D",
900: "#7A2E0E"
},
purple: {
25: "#FAFAFF",
50: "#F4F3FF",
100: "#EBE9FE",
200: "#D9D6FE",
300: "#BDB4FE",
400: "#9B8AFB",
500: "#7A5AF8",
600: "#6938EF",
700: "#5925DC",
800: "#4A1FB8",
900: "#3E1C96"
},
teal: {
25: "#F6FEFC",
50: "#F0FDF9",
100: "#CCFBEF",
200: "#99F6E0",
300: "#5FE9D0",
400: "#2ED3B7",
500: "#15B79E",
600: "#0E9384",
700: "#107569",
800: "#125D56",
900: "#134E48"
},
pink: {
25: "#fdf2f8",
50: "#fce7f3",
100: "#fbcfe8",
200: "#f9a8d4",
300: "#f472b6",
400: "#ec4899",
500: "#db2777",
600: "#be185d",
700: "#9d174d",
800: "#831843",
900: "#500724"
},
cyan: {
25: "#ecfeff",
50: "#cffafe",
100: "#a5f3fc",
200: "#67e8f9",
300: "#22d3ee",
400: "#06b6d4",
500: "#0891b2",
600: "#0e7490",
700: "#155e75",
800: "#164e63",
900: "#083344"
}
},
alpha: {
100: "ff",
90: "e5",
80: "cc",
70: "b3",
60: "99",
50: "80",
40: "66",
30: "4d",
20: "33",
10: "1a",
0: "00"
},
font: {
size: {
"2xs": "calc(var(--tsrd-font-size) * 0.625)",
xs: "calc(var(--tsrd-font-size) * 0.75)",
sm: "calc(var(--tsrd-font-size) * 0.875)",
md: "var(--tsrd-font-size)",
lg: "calc(var(--tsrd-font-size) * 1.125)",
xl: "calc(var(--tsrd-font-size) * 1.25)",
"2xl": "calc(var(--tsrd-font-size) * 1.5)",
"3xl": "calc(var(--tsrd-font-size) * 1.875)",
"4xl": "calc(var(--tsrd-font-size) * 2.25)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.75)",
"7xl": "calc(var(--tsrd-font-size) * 4.5)",
"8xl": "calc(var(--tsrd-font-size) * 6)",
"9xl": "calc(var(--tsrd-font-size) * 8)"
},
lineHeight: {
"3xs": "calc(var(--tsrd-font-size) * 0.75)",
"2xs": "calc(var(--tsrd-font-size) * 0.875)",
xs: "calc(var(--tsrd-font-size) * 1)",
sm: "calc(var(--tsrd-font-size) * 1.25)",
md: "calc(var(--tsrd-font-size) * 1.5)",
lg: "calc(var(--tsrd-font-size) * 1.75)",
xl: "calc(var(--tsrd-font-size) * 2)",
"2xl": "calc(var(--tsrd-font-size) * 2.25)",
"3xl": "calc(var(--tsrd-font-size) * 2.5)",
"4xl": "calc(var(--tsrd-font-size) * 2.75)",
"5xl": "calc(var(--tsrd-font-size) * 3)",
"6xl": "calc(var(--tsrd-font-size) * 3.25)",
"7xl": "calc(var(--tsrd-font-size) * 3.5)",
"8xl": "calc(var(--tsrd-font-size) * 3.75)",
"9xl": "calc(var(--tsrd-font-size) * 4)"
},
weight: {
thin: "100",
extralight: "200",
light: "300",
normal: "400",
medium: "500",
semibold: "600",
bold: "700",
extrabold: "800",
black: "900"
},
fontFamily: {
sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif",
mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace`
}
},
breakpoints: {
xs: "320px",
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px"
},
border: { radius: {
none: "0px",
xs: "calc(var(--tsrd-font-size) * 0.125)",
sm: "calc(var(--tsrd-font-size) * 0.25)",
md: "calc(var(--tsrd-font-size) * 0.375)",
lg: "calc(var(--tsrd-font-size) * 0.5)",
xl: "calc(var(--tsrd-font-size) * 0.75)",
"2xl": "calc(var(--tsrd-font-size) * 1)",
"3xl": "calc(var(--tsrd-font-size) * 1.5)",
full: "9999px"
} },
size: {
0: "0px",
.25: "calc(var(--tsrd-font-size) * 0.0625)",
.5: "calc(var(--tsrd-font-size) * 0.125)",
1: "calc(var(--tsrd-font-size) * 0.25)",
1.5: "calc(var(--tsrd-font-size) * 0.375)",
2: "calc(var(--tsrd-font-size) * 0.5)",
2.5: "calc(var(--tsrd-font-size) * 0.625)",
3: "calc(var(--tsrd-font-size) * 0.75)",
3.5: "calc(var(--tsrd-font-size) * 0.875)",
4: "calc(var(--tsrd-font-size) * 1)",
4.5: "calc(var(--tsrd-font-size) * 1.125)",
5: "calc(var(--tsrd-font-size) * 1.25)",
5.5: "calc(var(--tsrd-font-size) * 1.375)",
6: "calc(var(--tsrd-font-size) * 1.5)",
6.5: "calc(var(--tsrd-font-size) * 1.625)",
7: "calc(var(--tsrd-font-size) * 1.75)",
8: "calc(var(--tsrd-font-size) * 2)",
9: "calc(var(--tsrd-font-size) * 2.25)",
10: "calc(var(--tsrd-font-size) * 2.5)",
11: "calc(var(--tsrd-font-size) * 2.75)",
12: "calc(var(--tsrd-font-size) * 3)",
14: "calc(var(--tsrd-font-size) * 3.5)",
16: "calc(var(--tsrd-font-size) * 4)",
20: "calc(var(--tsrd-font-size) * 5)",
24: "calc(var(--tsrd-font-size) * 6)",
28: "calc(var(--tsrd-font-size) * 7)",
32: "calc(var(--tsrd-font-size) * 8)",
36: "calc(var(--tsrd-font-size) * 9)",
40: "calc(var(--tsrd-font-size) * 10)",
44: "calc(var(--tsrd-font-size) * 11)",
48: "calc(var(--tsrd-font-size) * 12)",
52: "calc(var(--tsrd-font-size) * 13)",
56: "calc(var(--tsrd-font-size) * 14)",
60: "calc(var(--tsrd-font-size) * 15)",
64: "calc(var(--tsrd-font-size) * 16)",
72: "calc(var(--tsrd-font-size) * 18)",
80: "calc(var(--tsrd-font-size) * 20)",
96: "calc(var(--tsrd-font-size) * 24)"
},
shadow: {
xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`,
sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`,
md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`,
lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`,
xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`,
"2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`,
inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`,
none: () => `none`
},
zIndices: {
hide: -1,
auto: "auto",
base: 0,
docked: 10,
dropdown: 1e3,
sticky: 1100,
banner: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800
}
};
//#endregion
//#region src/styles/use-styles.ts
const stylesFactory = (theme) => {
const { colors, font, size, alpha, border } = tokens;
const { fontFamily, size: fontSize } = font;
const css = goober.css;
const t = (light, dark) => theme === "light" ? light : dark;
return {
/**
* Fills the devtools-utils portal div (height: 100%) and is a flex parent so MainPanel
* gets a real flex height budget (not just height: 100% without a flex chain).
*/
shellRoot: css`
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
`,
/** Overrides devtools-ui MainPanel overflow-y: auto so inner columns own scrolling */
mainPanelShell: css`
display: flex;
flex-direction: column;
flex: 1 1 0%;
min-height: 0;
overflow: hidden !important;
`,
mainContainer: css`
display: flex;
flex: 1 1 0%;
min-height: 0;
overflow: hidden;
padding: ${size[2]};
padding-top: 0;
margin-top: ${size[2]};
`,
dragHandle: css`
width: 8px;
background: ${t(colors.gray[300], colors.darkGray[600])};
cursor: col-resize;
position: relative;
transition: all 0.2s ease;
user-select: none;
pointer-events: all;
margin: 0 ${size[1]};
border-radius: 2px;
&:hover {
background: ${t(colors.blue[600], colors.blue[500])};
margin: 0 ${size[1]};
}
&.dragging {
background: ${t(colors.blue[700], colors.blue[600])};
margin: 0 ${size[1]};
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background: ${t(colors.gray[400], colors.darkGray[400])};
border-radius: 1px;
pointer-events: none;
}
&:hover::after,
&.dragging::after {
background: ${t(colors.blue[500], colors.blue[300])};
}
`,
leftPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex-shrink: 0;
`,
rightPanel: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
flex: 1;
`,
panelHeader: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
padding: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
background: ${t(colors.gray[100], colors.darkGray[800])};
flex-shrink: 0;
`,
utilList: css`
flex: 1;
overflow-y: auto;
padding: ${size[1]};
min-height: 0;
`,
utilGroup: css`
margin-bottom: ${size[2]};
`,
utilGroupHeader: css`
font-size: ${fontSize.xs};
font-weight: ${font.weight.semibold};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: ${size[1]};
padding: ${size[1]} ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
`,
utilRow: css`
display: flex;
justify-content: space-between;
align-items: center;
padding: ${size[2]};
margin-bottom: ${size[1]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[600])};
border-color: ${t(colors.gray[400], colors.darkGray[500])};
}
`,
utilRowSelected: css`
background: ${t(colors.blue[100], colors.blue[900] + alpha[20])};
border-color: ${t(colors.blue[600], colors.blue[500])};
box-shadow: 0 0 0 1px
${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])};
`,
utilKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
utilStatus: css`
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
text-transform: uppercase;
letter-spacing: 0.05em;
padding: ${size[1]} ${size[1]};
background: ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.sm};
margin-left: ${size[1]};
`,
stateDetails: css`
flex: 1;
overflow-y: auto;
padding: ${size[2]};
min-height: 0;
`,
stateHeader: css`
margin-bottom: ${size[2]};
padding-bottom: ${size[2]};
border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
`,
stateTitle: css`
font-size: ${fontSize.md};
font-weight: ${font.weight.bold};
color: ${t(colors.blue[700], colors.blue[400])};
margin-bottom: ${size[1]};
`,
stateKey: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[600], colors.gray[400])};
word-break: break-all;
`,
stateContent: css`
background: ${t(colors.gray[100], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[2]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
detailsGrid: css`
display: grid;
grid-template-columns: 1fr;
gap: ${size[2]};
align-items: start;
`,
detailSection: css`
background: ${t(colors.white, colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
border-radius: ${border.radius.md};
padding: ${size[2]};
`,
detailSectionHeader: css`
font-size: ${fontSize.sm};
font-weight: ${font.weight.bold};
color: ${t(colors.gray[800], colors.gray[200])};
margin-bottom: ${size[1]};
text-transform: uppercase;
letter-spacing: 0.04em;
`,
actionsRow: css`
display: flex;
flex-wrap: wrap;
gap: ${size[2]};
`,
actionButton: css`
display: inline-flex;
align-items: center;
gap: ${size[1]};
padding: ${size[1]} ${size[2]};
border-radius: ${border.radius.md};
border: 1px solid ${t(colors.gray[300], colors.darkGray[500])};
background: ${t(colors.gray[200], colors.darkGray[600])};
color: ${t(colors.gray[900], colors.gray[100])};
font-size: ${fontSize.xs};
cursor: pointer;
user-select: none;
transition:
background 0.15s,
border-color 0.15s;
&:hover {
background: ${t(colors.gray[300], colors.darkGray[500])};
border-color: ${t(colors.gray[400], colors.darkGray[400])};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background: ${t(colors.gray[200], colors.darkGray[600])};
border-color: ${t(colors.gray[300], colors.darkGray[500])};
}
}
`,
actionDotBlue: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.blue[400]};
`,
actionDotGreen: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.green[400]};
`,
actionDotRed: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.red[400]};
`,
actionDotYellow: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.yellow[400]};
`,
actionDotOrange: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.pink[400]};
`,
actionDotPurple: css`
width: 6px;
height: 6px;
border-radius: 9999px;
background: ${colors.purple[400]};
`,
infoGrid: css`
display: grid;
grid-template-columns: auto 1fr;
gap: ${size[1]};
row-gap: ${size[1]};
align-items: center;
`,
infoLabel: css`
color: ${t(colors.gray[600], colors.gray[400])};
font-size: ${fontSize.xs};
text-transform: uppercase;
letter-spacing: 0.05em;
`,
infoValueMono: css`
font-family: ${fontFamily.mono};
font-size: ${fontSize.xs};
color: ${t(colors.gray[900], colors.gray[100])};
word-break: break-all;
`,
noSelection: css`
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: ${t(colors.gray[500], colors.gray[500])};
font-style: italic;
text-align: center;
padding: ${size[4]};
`,
sectionContainer: css`
display: flex;
flex-wrap: wrap;
gap: ${size[4]};
`,
section: css`
background: ${t(colors.gray[100], colors.darkGray[800])};
border-radius: ${border.radius.lg};
box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))};
padding: ${size[4]};
margin-bottom: ${size[4]};
border: 1px solid ${t(colors.gray[200], colors.darkGray[700])};
min-width: 0;
max-width: 33%;
max-height: fit-content;
`,
sectionHeader: css`
font-size: ${fontSize.lg};
font-weight: ${font.weight.bold};
margin-bottom: ${size[2]};
color: ${t(colors.blue[600], colors.blue[400])};
letter-spacing: 0.01em;
display: flex;
align-items: center;
gap: ${size[2]};
`,
sectionEmpty: css`
color: ${t(colors.gray[500], colors.gray[500])};
font-size: ${fontSize.sm};
font-style: italic;
margin: ${size[2]} 0;
`,
instanceList: css`
display: flex;
flex-direction: column;
gap: ${size[2]};
background: ${t(colors.gray[200], colors.darkGray[700])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
`,
instanceCard: css`
background: ${t(colors.gray[200], colors.darkGray[700])};
border-radius: ${border.radius.md};
padding: ${size[3]};
border: 1px solid ${t(colors.gray[300], colors.darkGray[600])};
font-size: ${fontSize.sm};
color: ${t(colors.gray[900], colors.gray[100])};
font-family: ${fontFamily.mono};
overflow-x: auto;
transition:
box-shadow 0.3s,
background 0.3s;
`
};
};
function useStyles() {
const { theme } = useTheme();
const [styles, setStyles] = createSignal(stylesFactory(theme()));
createEffect(() => {
setStyles(stylesFactory(theme()));
});
return styles;
}
//#endregion
//#region src/components/util-groups.ts
const UTIL_GROUPS = [
{
key: "debouncers",
label: "Debouncers",
displayName: "Debouncer"
},
{
key: "throttlers",
label: "Throttlers",
displayName: "Throttler"
},
{
key: "batchers",
label: "Batchers",
displayName: "Batcher"
},
{
key: "queuers",
label: "Queuers",
displayName: "Queuer"
},
{
key: "rateLimiters",
label: "Rate Limiters",
displayName: "Rate Limiter"
},
{
key: "asyncDebouncers",
label: "Async Debouncers",
displayName: "Async Debouncer"
},
{
key: "asyncThrottlers",
label: "Async Throttlers",
displayName: "Async Throttler"
},
{
key: "asyncBatchers",
label: "Async Batchers",
displayName: "Async Batcher"
},
{
key: "asyncQueuers",
label: "Async Queuers",
displayName: "Async Queuer"
},
{
key: "asyncRateLimiters",
label: "Async Rate Limiters",
displayName: "Async Rate Limiter"
}
];
//#endregion
//#region src/components/UtilList.tsx
var _tmpl$$4 = /* @__PURE__ */ template(`<div>`), _tmpl$2$3 = /* @__PURE__ */ template(`<div><div>`), _tmpl$3$2 = /* @__PURE__ */ template(`<div><div></div><div>`);
function UtilList(props) {
const styles = useStyles();
return (() => {
var _el$ = _tmpl$$4();
insert(_el$, createComponent(For, {
each: UTIL_GROUPS,
children: (group) => memo(() => memo(() => props.utilState()[group.key].length > 0)() && (() => {
var _el$2 = _tmpl$2$3(), _el$3 = _el$2.firstChild;
insert(_el$3, () => group.label);
insert(_el$2, createComponent(For, {
get each() {
return props.utilState()[group.key];
},
children: (instance) => {
const status = (() => {
try {
const statusAccessor = useSelector(instance.store, (s) => s.status);
return () => statusAccessor() ?? "unknown";
} catch {
return () => "unknown";
}
})();
return (() => {
var _el$4 = _tmpl$3$2(), _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling;
_el$4.$$click = () => props.setSelectedKey(instance.key ?? null);
insert(_el$5, () => instance.key);
insert(_el$6, status);
effect((_p$) => {
var _v$3 = clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), _v$4 = styles().utilKey, _v$5 = styles().utilStatus;
_v$3 !== _p$.e && className(_el$4, _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$5, _p$.t = _v$4);
_v$5 !== _p$.a && className(_el$6, _p$.a = _v$5);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$4;
})();
}
}), null);
effect((_p$) => {
var _v$ = styles().utilGroup, _v$2 = styles().utilGroupHeader;
_v$ !== _p$.e && className(_el$2, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$2;
})())
}));
effect(() => className(_el$, styles().utilList));
return _el$;
})();
}
delegateEvents(["click"]);
//#endregion
//#region src/utils/read-pacer-store-state.ts
/** TanStack Store (`@tanstack/store`) used by pacer utils. */
function isPacerUtilTanStackStore(x) {
return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function";
}
/**
* Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may
* also expose a reactive `state` field. Use this for devtools UI that needs a
* plain snapshot (e.g. JsonTree).
*/
function getPacerUtilStoreState(instance) {
const store = instance.store;
if (store && typeof store.get === "function") return store.get();
if (store && store.state !== void 0) return store.state;
return instance.state;
}
//#endregion
//#region src/components/ActionButtons.tsx
var _tmpl$$3 = /* @__PURE__ */ template(`<div>No actions available for this util`), _tmpl$2$2 = /* @__PURE__ */ template(`<div>`), _tmpl$3$1 = /* @__PURE__ */ template(`<button><span>`), _tmpl$4 = /* @__PURE__ */ template(`<button><span></span>Flush`), _tmpl$5 = /* @__PURE__ */ template(`<button><span></span>Cancel`), _tmpl$6 = /* @__PURE__ */ template(`<button><span></span>Reset`), _tmpl$7 = /* @__PURE__ */ template(`<button><span></span>Clear`);
function ActionButtons(props) {
const styles = useStyles();
const utilInstance = props.instance;
const store = utilInstance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(utilInstance);
const getState = () => stateAccessor();
const hasPending = () => {
return "isPending" in getState();
};
const isPending = () => {
const u = getState();
return "isPending" in u ? !!u.isPending : false;
};
const isEmptyFlag = () => {
const u = getState();
return "isEmpty" in u ? !!u.isEmpty : false;
};
const hasFlush = typeof utilInstance.flush === "function";
const hasCancel = typeof utilInstance.cancel === "function";
const hasReset = typeof utilInstance.reset === "function";
const hasClear = typeof utilInstance.clear === "function";
const hasStart = typeof utilInstance.start === "function";
const hasStop = typeof utilInstance.stop === "function";
const hasStartStop = hasStart && hasStop;
const isRunning = () => {
const u = getState();
return "isRunning" in u ? !!u.isRunning : true;
};
if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return (() => {
var _el$ = _tmpl$$3();
effect(() => className(_el$, styles().sectionEmpty));
return _el$;
})();
const emitName = `d-${props.utilName}`;
const utilNameLower = props.utilName.toLowerCase();
return (() => {
var _el$2 = _tmpl$2$2();
insert(_el$2, (() => {
var _c$ = memo(() => !!hasPending());
return () => _c$() && (() => {
var _el$3 = _tmpl$3$1(), _el$4 = _el$3.firstChild;
_el$3.$$mousedown = () => {
const next = !isPending();
utilInstance.store.setState((prev) => ({
...prev,
isPending: next
}));
emitChange(emitName, utilInstance);
};
insert(_el$3, () => isPending() ? "Restore Pending" : "Trigger Pending", null);
effect((_p$) => {
var _v$ = styles().actionButton, _v$2 = styles().actionDotBlue;
_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$2, hasFlush && (() => {
var _el$5 = _tmpl$4(), _el$6 = _el$5.firstChild;
_el$5.$$mousedown = () => {
utilInstance.flush();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$3 = styles().actionButton, _v$4 = utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$5 = styles().actionDotGreen;
_v$3 !== _p$.e && className(_el$5, _p$.e = _v$3);
_v$4 !== _p$.t && (_el$5.disabled = _p$.t = _v$4);
_v$5 !== _p$.a && className(_el$6, _p$.a = _v$5);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$5;
})(), null);
insert(_el$2, hasCancel && (() => {
var _el$7 = _tmpl$5(), _el$8 = _el$7.firstChild;
_el$7.$$mousedown = () => {
utilInstance.cancel();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$6 = styles().actionButton, _v$7 = utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$8 = styles().actionDotRed;
_v$6 !== _p$.e && className(_el$7, _p$.e = _v$6);
_v$7 !== _p$.t && (_el$7.disabled = _p$.t = _v$7);
_v$8 !== _p$.a && className(_el$8, _p$.a = _v$8);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$7;
})(), null);
insert(_el$2, hasReset && (() => {
var _el$9 = _tmpl$6(), _el$0 = _el$9.firstChild;
_el$9.$$mousedown = () => {
utilInstance.reset();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$9 = styles().actionButton, _v$0 = styles().actionDotYellow;
_v$9 !== _p$.e && className(_el$9, _p$.e = _v$9);
_v$0 !== _p$.t && className(_el$0, _p$.t = _v$0);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$9;
})(), null);
insert(_el$2, hasClear && (() => {
var _el$1 = _tmpl$7(), _el$10 = _el$1.firstChild;
_el$1.$$mousedown = () => {
utilInstance.clear();
emitChange(emitName, utilInstance);
};
effect((_p$) => {
var _v$1 = styles().actionButton, _v$10 = utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$11 = styles().actionDotOrange;
_v$1 !== _p$.e && className(_el$1, _p$.e = _v$1);
_v$10 !== _p$.t && (_el$1.disabled = _p$.t = _v$10);
_v$11 !== _p$.a && className(_el$10, _p$.a = _v$11);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$1;
})(), null);
insert(_el$2, hasStartStop && (() => {
var _el$11 = _tmpl$3$1(), _el$12 = _el$11.firstChild;
_el$11.$$mousedown = () => {
if (isRunning()) utilInstance.stop();
else utilInstance.start();
emitChange(emitName, utilInstance);
};
insert(_el$11, () => isRunning() ? "Stop" : "Start", null);
effect((_p$) => {
var _v$12 = styles().actionButton, _v$13 = styles().actionDotPurple;
_v$12 !== _p$.e && className(_el$11, _p$.e = _v$12);
_v$13 !== _p$.t && className(_el$12, _p$.t = _v$13);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$11;
})(), null);
effect(() => className(_el$2, styles().actionsRow));
return _el$2;
})();
}
delegateEvents(["mousedown"]);
//#endregion
//#region src/components/StateHeader.tsx
var _tmpl$$2 = /* @__PURE__ */ template(`<div><div></div><div style=display:flex;align-items:center;gap:16px><div><div>Key</div><div></div><div>Last Updated</div><div> (<!>)</div></div><div style=margin-left:auto;font-weight:bold>% reduction`);
dayjs.extend(relativeTime);
function reductionFromState(entry, state) {
if (!state) return 0;
const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0;
if (entry.type.toLowerCase().includes("batcher")) {
const totalItemsProcessed = Number(state.totalItemsProcessed) || 0;
if (totalItemsProcessed === 0) return 0;
return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
}
let requestCount = 0;
if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0;
else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0;
else return 0;
if (requestCount === 0) return 0;
const reduction = requestCount - completedExecutions;
return Math.round(reduction / requestCount * 100);
}
function StateHeaderInner(props) {
const key = props.entry.instance.key;
const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now();
const getRelativeTime = () => {
const at = updatedAt();
const diffMs = props.now() - at;
const diffSeconds = Math.floor(diffMs / 1e3);
if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
return dayjs(at).fromNow();
};
const store = props.entry.instance?.store;
const stateAccessor = isPacerUtilTanStackStore(store) ? useSelector(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}, { compare: shallow }) : () => getPacerUtilStoreState(props.entry.instance);
const reductionPercentage = () => reductionFromState(props.entry, stateAccessor());
return (() => {
var _el$ = _tmpl$$2(), _el$2 = _el$.firstChild, _el$4 = _el$2.nextSibling.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.firstChild, _el$1 = _el$9.nextSibling;
_el$1.nextSibling;
var _el$10 = _el$4.nextSibling, _el$11 = _el$10.firstChild;
insert(_el$2, () => props.entry.type);
insert(_el$6, key);
insert(_el$8, () => new Date(updatedAt()).toLocaleTimeString(), _el$9);
insert(_el$8, getRelativeTime, _el$1);
insert(_el$10, reductionPercentage, _el$11);
effect((_p$) => {
var _v$ = props.styles.stateHeader, _v$2 = props.styles.stateTitle, _v$3 = props.styles.infoGrid, _v$4 = props.styles.infoLabel, _v$5 = props.styles.infoValueMono, _v$6 = props.styles.infoLabel, _v$7 = props.styles.infoValueMono, _v$8 = props.styles.infoValueMono;
_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$4, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$5, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$6, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$7, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$8, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$10, _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 StateHeader(props) {
const styles = useStyles();
const [now, setNow] = createSignal(Date.now());
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now());
}, 1e3);
onCleanup(() => {
clearInterval(interval);
});
});
return createComponent(Show, {
get when() {
return props.selectedInstance();
},
children: (entry) => createComponent(StateHeaderInner, {
get entry() {
return entry();
},
get styles() {
return styles();
},
now,
get utilState() {
return props.utilState;
}
})
});
}
//#endregion
//#region src/components/UtilStateJsonTree.tsx
/**
* Subscribes to the util's TanStack Store so the tree updates live (same
* idea as {@link UtilList} status). A plain snapshot would only refresh when the
* devtools shell re-renders, which does not happen on nested store updates.
*/
function UtilStateJsonTree(props) {
const store = props.instance?.store;
if (isPacerUtilTanStackStore(store)) {
const snapshot = useSelector(store, (s) => s);
return createComponent(JsonTree, { get value() {
return snapshot();
} });
}
return createComponent(JsonTree, { get value() {
return getPacerUtilStoreState(props.instance);
} });
}
//#endregion
//#region src/components/DetailsPanel.tsx
var _tmpl$$1 = /* @__PURE__ */ template(`<div>`), _tmpl$2$1 = /* @__PURE__ */ template(`<div>Select a util from the left panel to view its state`), _tmpl$3 = /* @__PURE__ */ template(`<div><div><div>Actions</div></div><div><div>State</div><div></div></div><div><div>Options</div><div>`);
function DetailsPanel(props) {
const styles = useStyles();
return (() => {
var _el$ = _tmpl$$1();
insert(_el$, () => {
const entry = props.selectedInstance();
if (!entry) return (() => {
var _el$2 = _tmpl$2$1();
effect(() => className(_el$2, styles().noSelection));
return _el$2;
})();
return [createComponent(StateHeader, {
get selectedInstance() {
return props.selectedInstance;
},
get utilState() {
return props.utilState;
}
}), (() => {
var _el$3 = _tmpl$3(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$4.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$9 = _el$6.nextSibling, _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling;
insert(_el$4, createComponent(ActionButtons, {
get instance() {
return entry.instance;
},
get utilName() {
return entry.type;
}
}), null);
insert(_el$8, createComponent(UtilStateJsonTree, { get instance() {
return entry.instance;
} }));
insert(_el$1, createComponent(JsonTree, { get value() {
return entry.instance.options;
} }));
effect((_p$) => {
var _v$ = styles().detailsGrid, _v$2 = styles().detailSection, _v$3 = styles().detailSectionHeader, _v$4 = styles().detailSection, _v$5 = styles().detailSectionHeader, _v$6 = styles().stateContent, _v$7 = styles().detailSection, _v$8 = styles().detailSectionHeader, _v$9 = styles().stateContent;
_v$ !== _p$.e && className(_el$3, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$4, _p$.t = _v$2);
_v$3 !== _p$.a && className(_el$5, _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$6, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$7, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$8, _p$.n = _v$6);
_v$7 !== _p$.s && className(_el$9, _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$0, _p$.h = _v$8);
_v$9 !== _p$.r && className(_el$1, _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
});
return _el$3;
})()];
});
effect(() => className(_el$, styles().stateDetails));
return _el$;
})();
}
//#endregion
//#region src/components/Shell.tsx
var _tmpl$ = /* @__PURE__ */ template(`<div><div style=min-width:150px;max-width:800px></div><div></div><div style=flex:1><div>Details`), _tmpl$2 = /* @__PURE__ */ template(`<div>`);
function readResizeObserverBlockSize(entry) {
const [box] = entry.borderBoxSize;
if (box !== void 0) return box.blockSize;
return entry.contentRect.height;
}
/** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
function resolvePluginHost(el) {
const byId = el.closest("[id^=\"plugin-container-\"]");
if (byId) return byId;
return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement;
}
function Shell() {
const styles = useStyles();
const state = usePacerDevtoolsState();
const utilState = () => state;
const [selectedKey, setSelectedKey] = createSignal(null);
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300);
const [isDragging, setIsDragging] = createSignal(false);
const [shellRootEl, setShellRootEl] = createSignal(null);
const [slotHeightPx, setSlotHeightPx] = createSignal(void 0);
const selectedInstance = createMemo(() => {
const key = selectedKey();
if (!key) return null;
for (const group of UTIL_GROUPS) {
const instance = utilState()[group.key].find((inst) => inst.key === key);
if (instance) return {
instance,
type: group.displayName
};
}
return null;
});
let dragStartX = 0;
let dragStartWidth = 0;
const handleMouseDown = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
dragStartX = e.clientX;
dragStartWidth = leftPanelWidth();
};
const handleMouseMove = (e) => {
if (!isDragging()) return;
e.preventDefault();
const deltaX = e.clientX - dragStartX;
setLeftPanelWidth(Math.max(150, Math.min(800, dragStartWidth + deltaX)));
};
const handleMouseUp = () => {
setIsDragging(false);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
onMount(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
});
onCleanup(() => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
});
/**
* TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
* ancestors, so percentage height often never constrains our tree. Observing the
* outer `plugin-container-*` host (not the immediate React mount wrapper) and
* setting an explicit pixel height makes inner overflow-y regions scroll without
* capping to the mount div’s collapsed height.
*/
createEffect(() => {
const el = shellRootEl();
if (!el || typeof ResizeObserver === "undefined") return;
const pluginSlot = resolvePluginHost(el);
if (!pluginSlot) return;
const ro = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) return;
const target = entry.target;
const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry);
if (h > 0) setSlotHeightPx(Math.round(h));
});
ro.observe(pluginSlot);
onCleanup(() => ro.disconnect());
});
return (() => {
var _el$ = _tmpl$2();
use(setShellRootEl, _el$);
insert(_el$, createComponent(MainPanel, {
get ["class"]() {
return styles().mainPanelShell;
},
get children() {
return [createComponent(Header, { get children() {
return createComponent(HeaderLogo, {
flavor: {
light: "#84cc16",
dark: "#84cc16"
},
children: "TanStack Pacer"
});
} }), (() => {
var _el$2 = _tmpl$(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild;
insert(_el$3, createComponent(UtilList, {
selectedKey,
setSelectedKey,
utilState
}));
_el$4.$$mousedown = handleMouseDown;
insert(_el$5, createComponent(DetailsPanel, {
selectedInstance,
utilState
}), null);
effect((_p$) => {
var _v$ = styles().mainContainer, _v$2 = styles().leftPanel, _v$3 = `${leftPanelWidth()}px`, _v$4 = `${styles().dragHandle} ${isDragging() ? "dragging" : ""}`, _v$5 = styles().rightPanel, _v$6 = styles().panelHeader;
_v$ !== _p$.e && className(_el$2, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
_v$3 !== _p$.a && setStyleProperty(_el$3, "width", _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);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0
});
return _el$2;
})()];
}
}));
effect((_p$) => {
var _v$7 = styles().shellRoot, _v$8 = { ...slotHeightPx() !== void 0 ? {
height: `${slotHeightPx()}px`,
"max-height": `${slotHeightPx()}px`
} : {} };
_v$7 !== _p$.e && className(_el$, _p$.e = _v$7);
_p$.t = style(_el$, _v$8, _p$.t);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
})();
}
delegateEvents(["mousedown"]);
//#endregion
//#region src/components/index.tsx
function PacerDevtools(props) {
return createComponent(ThemeContextProvider, {
get theme() {
return props.theme;
},
get children() {
return createComponent(PacerContextProvider, { get children() {
return createComponent(Shell, {});
} });
}
});
}
//#endregion
export { PacerDevtools as default };