Sign In

@tanstack/pacer-devtools

Package Overview
Dependencies
Maintainers
6
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.1.5
to
1.2.0
+1428
dist/components-CGEHIK6F.js
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 { useStore } 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 {
shellRoot: css`
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
`,
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 = useStore(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) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => 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) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => 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 = useStore(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 };
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 { useStore } 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 {
shellRoot: css`
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
`,
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 = useStore(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) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => 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) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => 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 = useStore(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 { JsonTree } from '@tanstack/devtools-ui'
import { useStore } from '@tanstack/solid-store'
import {
getPacerUtilStoreState,
isPacerUtilTanStackStore,
} from '../utils/read-pacer-store-state'
import type { JSX } from 'solid-js'
type UtilStateJsonTreeProps = {
instance: any
}
/**
* 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.
*/
export function UtilStateJsonTree(props: UtilStateJsonTreeProps): JSX.Element {
const store = props.instance?.store
if (isPacerUtilTanStackStore(store)) {
const snapshot = useStore(store as never, (s: unknown) => s)
return <JsonTree value={snapshot()} />
}
return <JsonTree value={getPacerUtilStoreState(props.instance)} />
}
/** TanStack Store (`@tanstack/store`) used by pacer utils. */
export function isPacerUtilTanStackStore(x: unknown): x is {
subscribe: (cb: (s: unknown) => void) => { unsubscribe: () => void }
get: () => unknown
} {
return (
typeof x === 'object' &&
x !== null &&
'subscribe' in x &&
typeof (x as { subscribe: unknown }).subscribe === 'function' &&
'get' in x &&
typeof (x as { get: unknown }).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).
*/
export function getPacerUtilStoreState(instance: {
store?: { get?: () => unknown; state?: unknown }
state?: unknown
}): unknown {
const store = instance.store
if (store && typeof store.get === 'function') {
return store.get()
}
if (store && store.state !== undefined) {
return store.state
}
return instance.state
}
+4
-8

@@ -0,14 +1,10 @@

import { ClassType } from "@tanstack/devtools-utils/solid";
//#region src/core.d.ts
interface PacerDevtoolsInit {}
declare const PacerDevtoolsCore$1: ClassType;
//#endregion
//#region src/index.d.ts
declare const PacerDevtoolsCore: new () => {
"__#private@#isMounted": boolean;
"__#private@#isMounting": boolean;
"__#private@#abortMount": boolean;
"__#private@#dispose"?: () => void;
mount<T extends HTMLElement>(el: T, props: TanStackDevtoolsPluginProps): Promise<void>;
unmount(): void;
};
declare const PacerDevtoolsCore: typeof PacerDevtoolsCore$1;
//#endregion
export { PacerDevtoolsCore, type PacerDevtoolsInit };
"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const [PacerDevtoolsCore$1, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("./components-DDmnxkL2.js"));
const coreClasses = constructCoreClass(() => import("./components-CGEHIK6F.js"));
const PacerDevtoolsCore$1 = coreClasses[0];
const PacerDevtoolsCoreNoOp = coreClasses[1];
//#endregion

@@ -6,0 +8,0 @@ //#region src/index.ts

@@ -0,19 +1,7 @@

import { ClassType } from "@tanstack/devtools-utils/solid";
//#region src/core.d.ts
interface PacerDevtoolsInit {}
declare const PacerDevtoolsCore: new () => {
"__#private@#isMounted": boolean;
"__#private@#isMounting": boolean;
"__#private@#abortMount": boolean;
"__#private@#dispose"?: () => void;
mount<T extends HTMLElement>(el: T, props: TanStackDevtoolsPluginProps): Promise<void>;
unmount(): void;
}, PacerDevtoolsCoreNoOp: new () => {
mount<T extends HTMLElement>(_el: T, _props: TanStackDevtoolsPluginProps): Promise<void>;
unmount(): void;
"__#private@#isMounted": boolean;
"__#private@#isMounting": boolean;
"__#private@#abortMount": boolean;
"__#private@#dispose"?: () => void;
};
declare const PacerDevtoolsCore: ClassType;
//#endregion
export { PacerDevtoolsCore, type PacerDevtoolsInit };
"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("../components-DDmnxkL2.js"));
const coreClasses = constructCoreClass(() => import("../components-CGEHIK6F.js"));
const PacerDevtoolsCore = coreClasses[0];
coreClasses[1];
//#endregion
export { PacerDevtoolsCore };
"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("../components-ByKT1JiI.js"));
const coreClasses = constructCoreClass(() => import("../components-vap4_VJT.js"));
const PacerDevtoolsCore = coreClasses[0];
coreClasses[1];
//#endregion
export { PacerDevtoolsCore };
"use client";
import { constructCoreClass } from "@tanstack/devtools-utils/solid";
//#region src/core.tsx
const [PacerDevtoolsCore$1, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("./components-ByKT1JiI.js"));
const coreClasses = constructCoreClass(() => import("./components-vap4_VJT.js"));
const PacerDevtoolsCore$1 = coreClasses[0];
const PacerDevtoolsCoreNoOp = coreClasses[1];
//#endregion

@@ -6,0 +8,0 @@ //#region src/index.ts

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

@@ -76,6 +76,6 @@ "author": "Tanner Linsley",

"@tanstack/devtools-utils": "^0.4.0",
"@tanstack/solid-store": "^0.8.1",
"@tanstack/solid-store": "^0.9.2",
"clsx": "^2.1.1",
"csstype": "^3.0.10",
"dayjs": "^1.11.19",
"csstype": "^3.2.3",
"dayjs": "^1.11.20",
"goober": "^2.1.18",

@@ -86,4 +86,4 @@ "solid-js": "^1.9.11"

"rolldown-plugin-solid": "^0.2.1",
"tsdown": "^0.21.1",
"vite-plugin-solid": "^2.11.10"
"tsdown": "^0.21.4",
"vite-plugin-solid": "^2.11.11"
},

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

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

import { pacerEventClient } from '@tanstack/pacer'
import { emitChange } from '@tanstack/pacer'
import { useStore } from '@tanstack/solid-store'
import { useStyles } from '../styles/use-styles'
import {
getPacerUtilStoreState,
isPacerUtilTanStackStore,
} from '../utils/read-pacer-store-state'
import type { PacerEventName } from '@tanstack/pacer'

@@ -13,8 +18,27 @@

const utilInstance = props.instance
const utilState = utilInstance?.store?.state
const hasPending = utilState && 'isPending' in utilState
const hasEmpty = utilState && 'isEmpty' in utilState
const isPending = hasPending ? !!utilState.isPending : false
const isEmpty = hasEmpty ? utilState.isEmpty : undefined
const store = utilInstance?.store
const stateAccessor = isPacerUtilTanStackStore(store)
? useStore(store as never, (s: unknown) =>
s !== null && s !== undefined && typeof s === 'object'
? (s as Record<string, unknown>)
: {},
)
: () => getPacerUtilStoreState(utilInstance) as Record<string, unknown>
const getState = () => stateAccessor()
const hasPending = () => {
const u = getState()
return 'isPending' in u
}
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'

@@ -28,5 +52,8 @@ const hasCancel = typeof utilInstance.cancel === 'function'

const isRunning = utilState?.isRunning ?? true
const isRunning = () => {
const u = getState()
return 'isRunning' in u ? !!u.isRunning : true
}
if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) {
if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) {
return (

@@ -40,10 +67,11 @@ <div class={styles().sectionEmpty}>

const emitName = `d-${props.utilName}` as PacerEventName
const utilNameLower = props.utilName.toLowerCase()
return (
<div class={styles().actionsRow}>
{hasPending && (
{hasPending() && (
<button
class={styles().actionButton}
onMouseDown={() => {
const next = !isPending
const next = !isPending()
utilInstance.store.setState((prev: any) => ({

@@ -53,7 +81,7 @@ ...prev,

}))
pacerEventClient.emit(emitName, utilInstance)
emitChange(emitName, utilInstance)
}}
>
<span class={styles().actionDotBlue} />
{isPending ? 'Restore Pending' : 'Trigger Pending'}
{isPending() ? 'Restore Pending' : 'Trigger Pending'}
</button>

@@ -66,11 +94,9 @@ )}

utilInstance.flush()
pacerEventClient.emit(emitName, utilInstance)
emitChange(emitName, utilInstance)
}}
disabled={
(props.utilName.toLowerCase().includes('debouncer') &&
!isPending) ||
(props.utilName.toLowerCase().includes('throttler') &&
!isPending) ||
(props.utilName.toLowerCase().includes('batcher') && isEmpty) ||
(props.utilName.toLowerCase().includes('queuer') && isEmpty)
(utilNameLower.includes('debouncer') && !isPending()) ||
(utilNameLower.includes('throttler') && !isPending()) ||
(utilNameLower.includes('batcher') && isEmptyFlag()) ||
(utilNameLower.includes('queuer') && isEmptyFlag())
}

@@ -87,11 +113,9 @@ >

utilInstance.cancel()
pacerEventClient.emit(emitName, utilInstance)
emitChange(emitName, utilInstance)
}}
disabled={
(props.utilName.toLowerCase().includes('debouncer') &&
!isPending) ||
(props.utilName.toLowerCase().includes('throttler') &&
!isPending) ||
(props.utilName.toLowerCase().includes('batcher') && isEmpty) ||
(props.utilName.toLowerCase().includes('queuer') && isEmpty)
(utilNameLower.includes('debouncer') && !isPending()) ||
(utilNameLower.includes('throttler') && !isPending()) ||
(utilNameLower.includes('batcher') && isEmptyFlag()) ||
(utilNameLower.includes('queuer') && isEmptyFlag())
}

@@ -108,3 +132,3 @@ >

utilInstance.reset()
pacerEventClient.emit(emitName, utilInstance)
emitChange(emitName, utilInstance)
}}

@@ -121,7 +145,7 @@ >

utilInstance.clear()
pacerEventClient.emit(emitName, utilInstance)
emitChange(emitName, utilInstance)
}}
disabled={
(props.utilName.toLowerCase().includes('batcher') && isEmpty) ||
(props.utilName.toLowerCase().includes('queuer') && isEmpty)
(utilNameLower.includes('batcher') && isEmptyFlag()) ||
(utilNameLower.includes('queuer') && isEmptyFlag())
}

@@ -137,3 +161,3 @@ >

onMouseDown={() => {
if (isRunning) {
if (isRunning()) {
utilInstance.stop()

@@ -143,7 +167,7 @@ } else {

}
pacerEventClient.emit(emitName, utilInstance)
emitChange(emitName, utilInstance)
}}
>
<span class={styles().actionDotPurple} />
{isRunning ? 'Stop' : 'Start'}
{isRunning() ? 'Stop' : 'Start'}
</button>

@@ -150,0 +174,0 @@ )}

@@ -5,2 +5,3 @@ import { JsonTree } from '@tanstack/devtools-ui'

import { StateHeader } from './StateHeader'
import { UtilStateJsonTree } from './UtilStateJsonTree'

@@ -46,3 +47,3 @@ type DetailsPanelProps = {

<div class={styles().stateContent}>
<JsonTree value={entry.instance.store?.state as unknown} />
<UtilStateJsonTree instance={entry.instance} />
</div>

@@ -49,0 +50,0 @@ </div>

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

import { createMemo, createSignal, onCleanup, onMount } from 'solid-js'
import {
createEffect,
createMemo,
createSignal,
onCleanup,
onMount,
} from 'solid-js'
import { Header, HeaderLogo, MainPanel } from '@tanstack/devtools-ui'

@@ -10,2 +16,19 @@ import { useStyles } from '../styles/use-styles'

function readResizeObserverBlockSize(entry: ResizeObserverEntry): number {
const [box] = entry.borderBoxSize
if (box !== undefined) 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: HTMLElement): HTMLElement | null {
const byId = el.closest<HTMLElement>('[id^="plugin-container-"]')
if (byId) return byId
return (
el.parentElement?.parentElement?.parentElement ??
el.parentElement?.parentElement ??
el.parentElement
)
}
export function Shell() {

@@ -18,2 +41,8 @@ const styles = useStyles()

const [isDragging, setIsDragging] = createSignal(false)
const [shellRootEl, setShellRootEl] = createSignal<HTMLDivElement | null>(
null,
)
const [slotHeightPx, setSlotHeightPx] = createSignal<number | undefined>(
undefined,
)

@@ -70,41 +99,81 @@ const selectedInstance = createMemo(() => {

/**
* 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 as HTMLElement
const h =
target.clientHeight > 0
? target.clientHeight
: readResizeObserverBlockSize(entry)
if (h > 0) setSlotHeightPx(Math.round(h))
})
ro.observe(pluginSlot)
onCleanup(() => ro.disconnect())
})
return (
<MainPanel>
<Header>
<HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}>
TanStack Pacer
</HeaderLogo>
</Header>
<div
ref={setShellRootEl}
class={styles().shellRoot}
style={{
...(slotHeightPx() !== undefined
? {
height: `${slotHeightPx()}px`,
'max-height': `${slotHeightPx()}px`,
}
: {}),
}}
>
<MainPanel class={styles().mainPanelShell}>
<Header>
<HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}>
TanStack Pacer
</HeaderLogo>
</Header>
<div class={styles().mainContainer}>
<div
class={styles().leftPanel}
style={{
width: `${leftPanelWidth()}px`,
'min-width': '150px',
'max-width': '800px',
}}
>
<UtilList
selectedKey={selectedKey}
setSelectedKey={setSelectedKey}
utilState={utilState}
<div class={styles().mainContainer}>
<div
class={styles().leftPanel}
style={{
width: `${leftPanelWidth()}px`,
'min-width': '150px',
'max-width': '800px',
}}
>
<UtilList
selectedKey={selectedKey}
setSelectedKey={setSelectedKey}
utilState={utilState}
/>
</div>
<div
class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`}
onMouseDown={handleMouseDown}
/>
</div>
<div
class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`}
onMouseDown={handleMouseDown}
/>
<div class={styles().rightPanel} style={{ flex: 1 }}>
<div class={styles().panelHeader}>Details</div>
<DetailsPanel
selectedInstance={selectedInstance}
utilState={utilState}
/>
<div class={styles().rightPanel} style={{ flex: 1 }}>
<div class={styles().panelHeader}>Details</div>
<DetailsPanel
selectedInstance={selectedInstance}
utilState={utilState}
/>
</div>
</div>
</div>
</MainPanel>
</MainPanel>
</div>
)
}
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime.js'
import { createSignal, onCleanup, onMount } from 'solid-js'
import { Show, createSignal, onCleanup, onMount } from 'solid-js'
import { useStore } from '@tanstack/solid-store'
import { useStyles } from '../styles/use-styles'
import {
getPacerUtilStoreState,
isPacerUtilTanStackStore,
} from '../utils/read-pacer-store-state'

@@ -13,24 +18,49 @@ dayjs.extend(relativeTime)

export function StateHeader(props: StateHeaderProps) {
const styles = useStyles()
const [now, setNow] = createSignal(Date.now())
function reductionFromState(
entry: { type: string },
state: Record<string, unknown> | null | undefined,
): number {
if (!state) return 0
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now())
}, 1000)
const isAsync = entry.type.toLowerCase().includes('async')
const completedExecutions = isAsync
? Number(state.settleCount) || 0
: Number(state.executionCount) || 0
onCleanup(() => {
clearInterval(interval)
})
})
if (entry.type.toLowerCase().includes('batcher')) {
const totalItemsProcessed = Number(state.totalItemsProcessed) || 0
if (totalItemsProcessed === 0) return 0
return Math.round(
((totalItemsProcessed - completedExecutions) / totalItemsProcessed) * 100,
)
}
const entry = props.selectedInstance()
if (!entry) return null
let requestCount = 0
const key = entry.instance.key as string
const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now()
if (state.maybeExecuteCount !== undefined) {
requestCount = Number(state.maybeExecuteCount) || 0
} else if (state.addItemCount !== undefined) {
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: {
entry: { instance: any; type: string }
styles: ReturnType<ReturnType<typeof useStyles>>
now: () => number
utilState: () => { lastUpdatedByKey: Record<string, number> }
}) {
const key = props.entry.instance.key as string
const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now()
const getRelativeTime = () => {
const diffMs = now() - updatedAt
const at = updatedAt()
const diffMs = props.now() - at
const diffSeconds = Math.floor(diffMs / 1000)

@@ -42,63 +72,35 @@

return dayjs(updatedAt).fromNow()
return dayjs(at).fromNow()
}
const getReductionPercentage = () => {
const state = entry.instance.store?.state || entry.instance.state
if (!state) return 0
// Use settleCount for async utilities, executionCount for sync utilities
const isAsync = entry.type.toLowerCase().includes('async')
const completedExecutions = isAsync
? state.settleCount || 0
: state.executionCount || 0
// For batchers, calculate reduction based on items processed vs executions
if (entry.type.toLowerCase().includes('batcher')) {
const totalItemsProcessed = state.totalItemsProcessed || 0
if (totalItemsProcessed === 0) return 0
return Math.round(
((totalItemsProcessed - completedExecutions) / totalItemsProcessed) *
100,
const store = props.entry.instance?.store
const stateAccessor = isPacerUtilTanStackStore(store)
? useStore(store as never, (s: unknown) =>
s !== null && s !== undefined && typeof s === 'object'
? (s as Record<string, unknown>)
: {},
)
}
: () =>
getPacerUtilStoreState(props.entry.instance) as Record<string, unknown>
// For other utilities, calculate reduction based on request tracking
let requestCount = 0
const reductionPercentage = () =>
reductionFromState(props.entry, stateAccessor())
if (state.maybeExecuteCount !== undefined) {
requestCount = state.maybeExecuteCount
} else if (state.addItemCount !== undefined) {
requestCount = state.addItemCount
} else {
// For utilities that don't track requests, show 0%
return 0
}
if (requestCount === 0) return 0
const reduction = requestCount - completedExecutions
return Math.round((reduction / requestCount) * 100)
}
const reductionPercentage = getReductionPercentage()
return (
<div class={styles().stateHeader}>
<div class={styles().stateTitle}>{entry.type}</div>
<div class={props.styles.stateHeader}>
<div class={props.styles.stateTitle}>{props.entry.type}</div>
<div style={{ display: 'flex', 'align-items': 'center', gap: '16px' }}>
<div class={styles().infoGrid}>
<div class={styles().infoLabel}>Key</div>
<div class={styles().infoValueMono}>{key}</div>
<div class={styles().infoLabel}>Last Updated</div>
<div class={styles().infoValueMono}>
{new Date(updatedAt).toLocaleTimeString()} ({getRelativeTime()})
<div class={props.styles.infoGrid}>
<div class={props.styles.infoLabel}>Key</div>
<div class={props.styles.infoValueMono}>{key}</div>
<div class={props.styles.infoLabel}>Last Updated</div>
<div class={props.styles.infoValueMono}>
{new Date(updatedAt()).toLocaleTimeString()} ({getRelativeTime()})
</div>
</div>
<div
class={styles().infoValueMono}
class={props.styles.infoValueMono}
style={{ 'margin-left': 'auto', 'font-weight': 'bold' }}
>
{reductionPercentage}% reduction
{reductionPercentage()}% reduction
</div>

@@ -109,1 +111,29 @@ </div>

}
export function StateHeader(props: StateHeaderProps) {
const styles = useStyles()
const [now, setNow] = createSignal(Date.now())
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now())
}, 1000)
onCleanup(() => {
clearInterval(interval)
})
})
return (
<Show when={props.selectedInstance()}>
{(entry) => (
<StateHeaderInner
entry={entry()}
styles={styles()}
now={now}
utilState={props.utilState}
/>
)}
</Show>
)
}
import { constructCoreClass } from '@tanstack/devtools-utils/solid'
import type { ClassType } from '@tanstack/devtools-utils/solid'
export interface PacerDevtoolsInit {}
const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(
() => import('./components'),
)
const coreClasses = constructCoreClass(() => import('./components'))
export { PacerDevtoolsCore, PacerDevtoolsCoreNoOp }
export const PacerDevtoolsCore: ClassType = coreClasses[0]
export const PacerDevtoolsCoreNoOp: ClassType = coreClasses[1]

@@ -5,3 +5,3 @@ 'use client'

export const PacerDevtoolsCore =
export const PacerDevtoolsCore: typeof Devtools.PacerDevtoolsCore =
process.env.NODE_ENV !== 'development'

@@ -8,0 +8,0 @@ ? Devtools.PacerDevtoolsCoreNoOp

@@ -1,4 +0,7 @@

import { createStore } from 'solid-js/store'
import { createStore, produce } from 'solid-js/store'
import { createContext, createEffect, onCleanup, useContext } from 'solid-js'
import { pacerEventClient } from '@tanstack/pacer/event-client'
import {
getPacerDevtoolsInstance,
pacerEventClient,
} from '@tanstack/pacer/event-client'
import type {

@@ -12,2 +15,3 @@ AsyncBatcher,

Debouncer,
PacerEventName,
Queuer,

@@ -53,15 +57,59 @@ RateLimiter,

const updateOrAddToArray = <T extends { key: string }>(
oldArray: Array<T>,
newItem: T,
) => {
const index = oldArray.findIndex((item) => item.key === newItem.key)
if (index !== -1) {
// Update existing item
return oldArray.map((item, i) => (i === index ? newItem : item))
}
// Add new item
return [...oldArray, newItem]
}
type UtilListKey = Exclude<keyof PacerDevtoolsContextType, 'lastUpdatedByKey'>
/**
* 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: Array<{
listKey: UtilListKey
suffixes: ReadonlyArray<PacerEventName>
}> = [
{
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'],
},
]
export function PacerContextProvider(props: { children: any }) {

@@ -73,117 +121,38 @@ const [store, setStore] = createStore<PacerDevtoolsContextType>(

createEffect(() => {
const cleanup = pacerEventClient.onAllPluginEvents((_e) => {
const e = _e as unknown as { type: string; payload: any }
switch (e.type) {
case 'pacer:AsyncBatcher': {
setStore({
asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:AsyncDebouncer': {
setStore({
asyncDebouncers: updateOrAddToArray(
store.asyncDebouncers,
e.payload,
),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:AsyncQueuer': {
setStore({
asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:AsyncRateLimiter': {
setStore({
asyncRateLimiters: updateOrAddToArray(
store.asyncRateLimiters,
e.payload,
),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:AsyncThrottler': {
setStore({
asyncThrottlers: updateOrAddToArray(
store.asyncThrottlers,
e.payload,
),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:Batcher': {
setStore({
batchers: updateOrAddToArray(store.batchers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:Debouncer': {
setStore({
debouncers: updateOrAddToArray(store.debouncers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:Queuer': {
setStore({
queuers: updateOrAddToArray(store.queuers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:RateLimiter': {
setStore({
rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
case 'pacer:Throttler': {
setStore({
throttlers: updateOrAddToArray(store.throttlers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now(),
},
})
break
}
const cleanups: Array<() => void> = []
for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) {
for (const suffix of suffixes) {
cleanups.push(
pacerEventClient.on(suffix, (e) => {
const payload = e.payload
const key = payload.key
if (!key) return
const instance = getPacerDevtoolsInstance(key)
if (!instance || typeof instance !== 'object') return
setStore(
produce((draft) => {
const list = draft[listKey] as Array<{ key: string }>
const index = list.findIndex((item) => item.key === key)
const inst = instance as { key: string }
if (index !== -1) {
list[index] = inst as (typeof list)[number]
} else {
list.push(inst as (typeof list)[number])
}
draft.lastUpdatedByKey[key] = Date.now()
}),
)
}),
)
}
}
onCleanup(() => {
for (const cleanup of cleanups) {
cleanup()
}
})
onCleanup(cleanup)
})

@@ -190,0 +159,0 @@ return (

@@ -13,5 +13,26 @@ import * as goober from 'goober'

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;
flex: 1 1 0%;
min-height: 0;

@@ -18,0 +39,0 @@ overflow: hidden;

import { createComponent, escape, ssr, ssrAttribute, ssrStyleProperty } from "solid-js/web";
import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui";
import { createStore } from "solid-js/store";
import { For, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
import { pacerEventClient } from "@tanstack/pacer/event-client";
import * as goober from "goober";
import { useStore } 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, () => {}]);
const updateOrAddToArray = (oldArray, newItem) => {
const index = oldArray.findIndex((item) => item.key === newItem.key);
if (index !== -1) return oldArray.map((item, i) => i === index ? newItem : item);
return [...oldArray, newItem];
};
function PacerContextProvider(props) {
const [store, setStore] = createStore(initialPacerDevtoolsStore);
createEffect(() => {
onCleanup(pacerEventClient.onAllPluginEvents((_e) => {
const e = _e;
switch (e.type) {
case "pacer:AsyncBatcher":
setStore({
asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncDebouncer":
setStore({
asyncDebouncers: updateOrAddToArray(store.asyncDebouncers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncQueuer":
setStore({
asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncRateLimiter":
setStore({
asyncRateLimiters: updateOrAddToArray(store.asyncRateLimiters, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncThrottler":
setStore({
asyncThrottlers: updateOrAddToArray(store.asyncThrottlers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Batcher":
setStore({
batchers: updateOrAddToArray(store.batchers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Debouncer":
setStore({
debouncers: updateOrAddToArray(store.debouncers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Queuer":
setStore({
queuers: updateOrAddToArray(store.queuers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:RateLimiter":
setStore({
rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Throttler":
setStore({
throttlers: updateOrAddToArray(store.throttlers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
}
}));
});
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 {
mainContainer: css`
display: flex;
flex: 1;
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$2 = [
"<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$2, 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 = useStore(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/components/ActionButtons.tsx
var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$1 = [
"<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 utilState = utilInstance?.store?.state;
const hasPending = utilState && "isPending" in utilState;
const hasEmpty = utilState && "isEmpty" in utilState;
const isPending = hasPending ? !!utilState.isPending : false;
const isEmpty = hasEmpty ? utilState.isEmpty : void 0;
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 = utilState?.isRunning ?? true;
if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false));
`${props.utilName}`;
return ssr(_tmpl$2$1, 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", props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, 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", props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, 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 StateHeader(props) {
const styles = useStyles();
const [now, setNow] = createSignal(Date.now());
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now());
}, 1e3);
onCleanup(() => {
clearInterval(interval);
});
});
const entry = props.selectedInstance();
if (!entry) return null;
const key = entry.instance.key;
const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now();
const getRelativeTime = () => {
const diffMs = now() - updatedAt;
const diffSeconds = Math.floor(diffMs / 1e3);
if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
return dayjs(updatedAt).fromNow();
};
const getReductionPercentage = () => {
const state = entry.instance.store?.state || entry.instance.state;
if (!state) return 0;
const completedExecutions = entry.type.toLowerCase().includes("async") ? state.settleCount || 0 : state.executionCount || 0;
if (entry.type.toLowerCase().includes("batcher")) {
const totalItemsProcessed = state.totalItemsProcessed || 0;
if (totalItemsProcessed === 0) return 0;
return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
}
let requestCount = 0;
if (state.maybeExecuteCount !== void 0) requestCount = state.maybeExecuteCount;
else if (state.addItemCount !== void 0) requestCount = state.addItemCount;
else return 0;
if (requestCount === 0) return 0;
const reduction = requestCount - completedExecutions;
return Math.round(reduction / requestCount * 100);
};
const reductionPercentage = getReductionPercentage();
return ssr(_tmpl$$2, ssrAttribute("class", escape(styles().stateHeader, true), false), ssrAttribute("class", escape(styles().stateTitle, true), false), escape(entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(styles().infoGrid, true), false), ssrAttribute("class", escape(styles().infoLabel, true), false), ssrAttribute("class", escape(styles().infoValueMono, true), false), escape(key), ssrAttribute("class", escape(styles().infoLabel, true), false), ssrAttribute("class", escape(styles().infoValueMono, true), false), escape(new Date(updatedAt).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(styles().infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage));
}
//#endregion
//#region src/components/DetailsPanel.tsx
var _tmpl$$1 = [
"<div",
">",
"</div>"
], _tmpl$2 = ["<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, 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(JsonTree, { get value() {
return entry.instance.store?.state;
} })), 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>"
];
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 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);
});
return createComponent(MainPanel, { 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, template } from "solid-js/web";
import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui";
import { createStore } from "solid-js/store";
import { For, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
import { pacerEventClient } from "@tanstack/pacer/event-client";
import * as goober from "goober";
import { useStore } from "@tanstack/solid-store";
import clsx from "clsx";
import { pacerEventClient as pacerEventClient$1 } 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, () => {}]);
const updateOrAddToArray = (oldArray, newItem) => {
const index = oldArray.findIndex((item) => item.key === newItem.key);
if (index !== -1) return oldArray.map((item, i) => i === index ? newItem : item);
return [...oldArray, newItem];
};
function PacerContextProvider(props) {
const [store, setStore] = createStore(initialPacerDevtoolsStore);
createEffect(() => {
onCleanup(pacerEventClient.onAllPluginEvents((_e) => {
const e = _e;
switch (e.type) {
case "pacer:AsyncBatcher":
setStore({
asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncDebouncer":
setStore({
asyncDebouncers: updateOrAddToArray(store.asyncDebouncers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncQueuer":
setStore({
asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncRateLimiter":
setStore({
asyncRateLimiters: updateOrAddToArray(store.asyncRateLimiters, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:AsyncThrottler":
setStore({
asyncThrottlers: updateOrAddToArray(store.asyncThrottlers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Batcher":
setStore({
batchers: updateOrAddToArray(store.batchers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Debouncer":
setStore({
debouncers: updateOrAddToArray(store.debouncers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Queuer":
setStore({
queuers: updateOrAddToArray(store.queuers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:RateLimiter":
setStore({
rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
case "pacer:Throttler":
setStore({
throttlers: updateOrAddToArray(store.throttlers, e.payload),
lastUpdatedByKey: {
...store.lastUpdatedByKey,
[e.payload.key]: Date.now()
}
});
break;
}
}));
});
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 {
mainContainer: css`
display: flex;
flex: 1;
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$2 = /* @__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$2(), _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 = useStore(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/components/ActionButtons.tsx
var _tmpl$$3 = /* @__PURE__ */ template(`<div>No actions available for this util`), _tmpl$2$1 = /* @__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 utilState = utilInstance?.store?.state;
const hasPending = utilState && "isPending" in utilState;
const hasEmpty = utilState && "isEmpty" in utilState;
const isPending = hasPending ? !!utilState.isPending : false;
const isEmpty = hasEmpty ? utilState.isEmpty : void 0;
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 = utilState?.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}`;
return (() => {
var _el$2 = _tmpl$2$1();
insert(_el$2, hasPending && (() => {
var _el$3 = _tmpl$3$1(), _el$4 = _el$3.firstChild;
_el$3.$$mousedown = () => {
const next = !isPending;
utilInstance.store.setState((prev) => ({
...prev,
isPending: next
}));
pacerEventClient$1.emit(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();
pacerEventClient$1.emit(emitName, utilInstance);
};
effect((_p$) => {
var _v$3 = styles().actionButton, _v$4 = props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, _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();
pacerEventClient$1.emit(emitName, utilInstance);
};
effect((_p$) => {
var _v$6 = styles().actionButton, _v$7 = props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, _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();
pacerEventClient$1.emit(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();
pacerEventClient$1.emit(emitName, utilInstance);
};
effect((_p$) => {
var _v$1 = styles().actionButton, _v$10 = props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, _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();
pacerEventClient$1.emit(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 StateHeader(props) {
const styles = useStyles();
const [now, setNow] = createSignal(Date.now());
onMount(() => {
const interval = setInterval(() => {
setNow(Date.now());
}, 1e3);
onCleanup(() => {
clearInterval(interval);
});
});
const entry = props.selectedInstance();
if (!entry) return null;
const key = entry.instance.key;
const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now();
const getRelativeTime = () => {
const diffMs = now() - updatedAt;
const diffSeconds = Math.floor(diffMs / 1e3);
if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
return dayjs(updatedAt).fromNow();
};
const getReductionPercentage = () => {
const state = entry.instance.store?.state || entry.instance.state;
if (!state) return 0;
const completedExecutions = entry.type.toLowerCase().includes("async") ? state.settleCount || 0 : state.executionCount || 0;
if (entry.type.toLowerCase().includes("batcher")) {
const totalItemsProcessed = state.totalItemsProcessed || 0;
if (totalItemsProcessed === 0) return 0;
return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
}
let requestCount = 0;
if (state.maybeExecuteCount !== void 0) requestCount = state.maybeExecuteCount;
else if (state.addItemCount !== void 0) requestCount = state.addItemCount;
else return 0;
if (requestCount === 0) return 0;
const reduction = requestCount - completedExecutions;
return Math.round(reduction / requestCount * 100);
};
const reductionPercentage = getReductionPercentage();
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, () => 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$ = styles().stateHeader, _v$2 = styles().stateTitle, _v$3 = styles().infoGrid, _v$4 = styles().infoLabel, _v$5 = styles().infoValueMono, _v$6 = styles().infoLabel, _v$7 = styles().infoValueMono, _v$8 = 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$;
})();
}
//#endregion
//#region src/components/DetailsPanel.tsx
var _tmpl$$1 = /* @__PURE__ */ template(`<div>`), _tmpl$2 = /* @__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();
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(JsonTree, { get value() {
return entry.instance.store?.state;
} }));
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`);
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 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);
});
return createComponent(MainPanel, { get children() {
return [createComponent(Header, { get children() {
return createComponent(HeaderLogo, {
flavor: {
light: "#84cc16",
dark: "#84cc16"
},
children: "TanStack Pacer"
});
} }), (() => {
var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.firstChild;
insert(_el$2, createComponent(UtilList, {
selectedKey,
setSelectedKey,
utilState
}));
_el$3.$$mousedown = handleMouseDown;
insert(_el$4, 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$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && setStyleProperty(_el$2, "width", _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$3, _p$.o = _v$4);
_v$5 !== _p$.i && className(_el$4, _p$.i = _v$5);
_v$6 !== _p$.n && className(_el$5, _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$;
})()];
} });
}
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 };