@tanstack/query-devtools 5.0.0-alpha.53 → 5.0.0-alpha.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cjs/Devtools-e25fdb3e.js +2633 -0
  2. package/dist/cjs/Devtools-e25fdb3e.js.map +1 -0
  3. package/dist/cjs/index.cjs +102 -0
  4. package/dist/cjs/index.cjs.map +1 -0
  5. package/dist/esm/Devtools-1e41c850.js +2625 -0
  6. package/dist/esm/Devtools-1e41c850.js.map +1 -0
  7. package/dist/esm/index.js +100 -0
  8. package/dist/esm/index.js.map +1 -0
  9. package/dist/source/Context.js +10 -0
  10. package/dist/source/Devtools.jsx +1508 -0
  11. package/dist/source/Explorer.jsx +264 -0
  12. package/dist/source/fonts.js +7 -0
  13. package/dist/source/icons/index.jsx +344 -0
  14. package/dist/source/index.jsx +88 -0
  15. package/dist/source/theme.js +304 -0
  16. package/dist/source/utils.jsx +77 -0
  17. package/{build/lib → dist/types}/Context.d.ts +0 -1
  18. package/{build/lib → dist/types}/Devtools.d.ts +1 -2
  19. package/{build/lib → dist/types}/Explorer.d.ts +0 -1
  20. package/{build/lib → dist/types}/fonts.d.ts +0 -1
  21. package/{build/lib → dist/types}/icons/index.d.ts +0 -1
  22. package/{build/lib → dist/types}/index.d.ts +2 -1
  23. package/{build/lib → dist/types}/theme.d.ts +0 -1
  24. package/{build/lib → dist/types}/utils.d.ts +5 -4
  25. package/package.json +18 -13
  26. package/src/Devtools.tsx +40 -0
  27. package/src/Explorer.tsx +16 -18
  28. package/src/index.tsx +14 -3
  29. package/src/utils.tsx +12 -2
  30. package/build/lib/Context.d.ts.map +0 -1
  31. package/build/lib/Devtools.d.ts.map +0 -1
  32. package/build/lib/Explorer.d.ts.map +0 -1
  33. package/build/lib/__tests__/devtools.test.d.ts +0 -1
  34. package/build/lib/__tests__/devtools.test.d.ts.map +0 -1
  35. package/build/lib/fonts.d.ts.map +0 -1
  36. package/build/lib/icons/index.d.ts.map +0 -1
  37. package/build/lib/index.cjs +0 -8074
  38. package/build/lib/index.cjs.map +0 -1
  39. package/build/lib/index.d.ts.map +0 -1
  40. package/build/lib/index.js +0 -8072
  41. package/build/lib/index.js.map +0 -1
  42. package/build/lib/theme.d.ts.map +0 -1
  43. package/build/lib/utils.d.ts.map +0 -1
  44. package/build/stats-html.html +0 -6177
  45. package/build/stats.json +0 -1699
@@ -0,0 +1,1508 @@
1
+ import { createEffect, createMemo, createSignal, on, onCleanup, onMount, For, Show, } from 'solid-js';
2
+ import { rankItem } from '@tanstack/match-sorter-utils';
3
+ import { css, cx } from '@emotion/css';
4
+ import { tokens } from './theme';
5
+ import { getQueryStatusLabel, getQueryStatusColor, displayValue, getQueryStatusColorByLabel, sortFns, convertRemToPixels, getSidedProp, } from './utils';
6
+ import { ArrowDown, ArrowUp, ChevronDown, Offline, Search, Settings, TanstackLogo, Wifi, } from './icons';
7
+ import Explorer from './Explorer';
8
+ import { QueryDevtoolsContext, useQueryDevtoolsContext } from './Context';
9
+ import { TransitionGroup } from 'solid-transition-group';
10
+ import { loadFonts } from './fonts';
11
+ import { Key } from '@solid-primitives/keyed';
12
+ import { createLocalStorage } from '@solid-primitives/storage';
13
+ import { createResizeObserver } from '@solid-primitives/resize-observer';
14
+ const firstBreakpoint = 1024;
15
+ const secondBreakpoint = 796;
16
+ const thirdBreakpoint = 700;
17
+ const BUTTON_POSITION = 'bottom-right';
18
+ const POSITION = 'bottom';
19
+ const INITIAL_IS_OPEN = false;
20
+ const DEFAULT_HEIGHT = 500;
21
+ const DEFAULT_WIDTH = 500;
22
+ const DEFAULT_SORT_FN_NAME = Object.keys(sortFns)[0];
23
+ const DEFAULT_SORT_ORDER = 1;
24
+ const [selectedQueryHash, setSelectedQueryHash] = createSignal(null);
25
+ const [panelWidth, setPanelWidth] = createSignal(0);
26
+ export const DevtoolsComponent = (props) => {
27
+ return (<QueryDevtoolsContext.Provider value={props}>
28
+ <Devtools />
29
+ </QueryDevtoolsContext.Provider>);
30
+ };
31
+ export default DevtoolsComponent;
32
+ export const Devtools = () => {
33
+ loadFonts();
34
+ const styles = getStyles();
35
+ const [localStore, setLocalStore] = createLocalStorage({
36
+ prefix: 'TanstackQueryDevtools',
37
+ });
38
+ const buttonPosition = createMemo(() => {
39
+ return useQueryDevtoolsContext().buttonPosition || BUTTON_POSITION;
40
+ });
41
+ const isOpen = createMemo(() => {
42
+ return localStore.open === 'true'
43
+ ? true
44
+ : localStore.open === 'false'
45
+ ? false
46
+ : useQueryDevtoolsContext().initialIsOpen || INITIAL_IS_OPEN;
47
+ });
48
+ const position = createMemo(() => {
49
+ return localStore.position || useQueryDevtoolsContext().position || POSITION;
50
+ });
51
+ return (<div
52
+ // styles for animating the panel in and out
53
+ class={css `
54
+ & .TSQD-panel-exit-active,
55
+ & .TSQD-panel-enter-active {
56
+ transition: opacity 0.3s, transform 0.3s;
57
+ }
58
+
59
+ & .TSQD-panel-exit-to,
60
+ & .TSQD-panel-enter {
61
+ ${position() === 'top'
62
+ ? `transform: translateY(-${Number(localStore.height || DEFAULT_HEIGHT)}px);`
63
+ : position() === 'left'
64
+ ? `transform: translateX(-${Number(localStore.width || DEFAULT_WIDTH)}px);`
65
+ : position() === 'right'
66
+ ? `transform: translateX(${Number(localStore.width || DEFAULT_WIDTH)}px);`
67
+ : `transform: translateY(${Number(localStore.height || DEFAULT_HEIGHT)}px);`}
68
+ }
69
+
70
+ & .TSQD-button-exit-active,
71
+ & .TSQD-button-enter-active {
72
+ transition: opacity 0.3s, transform 0.3s;
73
+ }
74
+
75
+ & .TSQD-button-exit-to,
76
+ & .TSQD-button-enter {
77
+ transform: ${buttonPosition() === 'top-left'
78
+ ? `translateX(-72px);`
79
+ : buttonPosition() === 'top-right'
80
+ ? `translateX(72px);`
81
+ : `translateY(72px);`};
82
+ }
83
+ `}>
84
+ <TransitionGroup name="TSQD-panel">
85
+ <Show when={isOpen()}>
86
+ <DevtoolsPanel localStore={localStore} setLocalStore={setLocalStore}/>
87
+ </Show>
88
+ </TransitionGroup>
89
+ <TransitionGroup name="TSQD-button">
90
+ <Show when={!isOpen()}>
91
+ <div class={cx(styles.devtoolsBtn, styles[`devtoolsBtn-position-${buttonPosition()}`])}>
92
+ <div aria-hidden="true">
93
+ <TanstackLogo />
94
+ </div>
95
+ <button aria-label="Open Tanstack query devtools" onClick={() => setLocalStore('open', 'true')}>
96
+ <TanstackLogo />
97
+ </button>
98
+ </div>
99
+ </Show>
100
+ </TransitionGroup>
101
+ </div>);
102
+ };
103
+ export const DevtoolsPanel = (props) => {
104
+ const styles = getStyles();
105
+ const [isResizing, setIsResizing] = createSignal(false);
106
+ const sort = createMemo(() => props.localStore.sort || DEFAULT_SORT_FN_NAME);
107
+ const sortOrder = createMemo(() => Number(props.localStore.sortOrder) || DEFAULT_SORT_ORDER);
108
+ const [offline, setOffline] = createSignal(false);
109
+ const [settingsOpen, setSettingsOpen] = createSignal(false);
110
+ const position = createMemo(() => (props.localStore.position ||
111
+ useQueryDevtoolsContext().position ||
112
+ POSITION));
113
+ const sortFn = createMemo(() => sortFns[sort()]);
114
+ const onlineManager = createMemo(() => useQueryDevtoolsContext().onlineManager);
115
+ const cache = createMemo(() => {
116
+ return useQueryDevtoolsContext().client.getQueryCache();
117
+ });
118
+ const queryCount = createSubscribeToQueryCacheBatcher((queryCache) => {
119
+ return queryCache().getAll().length;
120
+ }, false);
121
+ const queries = createMemo(on(() => [queryCount(), props.localStore.filter, sort(), sortOrder()], () => {
122
+ const curr = cache().getAll();
123
+ const filtered = props.localStore.filter
124
+ ? curr.filter((item) => rankItem(item.queryHash, props.localStore.filter || '').passed)
125
+ : [...curr];
126
+ const sorted = sortFn()
127
+ ? filtered.sort((a, b) => sortFn()(a, b) * sortOrder())
128
+ : filtered;
129
+ return sorted;
130
+ }));
131
+ const handleDragStart = (event) => {
132
+ const panelElement = event.currentTarget.parentElement;
133
+ if (!panelElement)
134
+ return;
135
+ setIsResizing(true);
136
+ const { height, width } = panelElement.getBoundingClientRect();
137
+ const startX = event.clientX;
138
+ const startY = event.clientY;
139
+ let newSize = 0;
140
+ const minHeight = convertRemToPixels(3.5);
141
+ const minWidth = convertRemToPixels(12);
142
+ const runDrag = (moveEvent) => {
143
+ moveEvent.preventDefault();
144
+ if (position() === 'left' || position() === 'right') {
145
+ const valToAdd = position() === 'right'
146
+ ? startX - moveEvent.clientX
147
+ : moveEvent.clientX - startX;
148
+ newSize = Math.round(width + valToAdd);
149
+ if (newSize < minWidth) {
150
+ newSize = minWidth;
151
+ }
152
+ props.setLocalStore('width', String(Math.round(newSize)));
153
+ const newWidth = panelElement.getBoundingClientRect().width;
154
+ // If the panel size didn't decrease, this means we have reached the minimum width
155
+ // of the panel so we restore the original width in local storage
156
+ // Restoring the width helps in smooth open/close transitions
157
+ if (Number(props.localStore.width) < newWidth) {
158
+ props.setLocalStore('width', String(newWidth));
159
+ }
160
+ }
161
+ else {
162
+ const valToAdd = position() === 'bottom'
163
+ ? startY - moveEvent.clientY
164
+ : moveEvent.clientY - startY;
165
+ newSize = Math.round(height + valToAdd);
166
+ // If the panel size is less than the minimum height,
167
+ // we set the size to the minimum height
168
+ if (newSize < minHeight) {
169
+ newSize = minHeight;
170
+ setSelectedQueryHash(null);
171
+ }
172
+ props.setLocalStore('height', String(Math.round(newSize)));
173
+ }
174
+ };
175
+ const unsub = () => {
176
+ if (isResizing()) {
177
+ setIsResizing(false);
178
+ }
179
+ document.removeEventListener('mousemove', runDrag, false);
180
+ document.removeEventListener('mouseUp', unsub, false);
181
+ };
182
+ document.addEventListener('mousemove', runDrag, false);
183
+ document.addEventListener('mouseup', unsub, false);
184
+ };
185
+ setupQueryCacheSubscription();
186
+ let queriesContainerRef;
187
+ let panelRef;
188
+ onMount(() => {
189
+ createResizeObserver(panelRef, ({ width }, el) => {
190
+ if (el === panelRef) {
191
+ setPanelWidth(width);
192
+ }
193
+ });
194
+ });
195
+ const setDevtoolsPosition = (pos) => {
196
+ props.setLocalStore('position', pos);
197
+ setSettingsOpen(false);
198
+ };
199
+ createEffect(() => {
200
+ const rootContainer = panelRef.parentElement?.parentElement?.parentElement;
201
+ if (!rootContainer)
202
+ return;
203
+ const styleProp = getSidedProp('padding', props.localStore.position);
204
+ const isVertical = props.localStore.position === 'left' ||
205
+ props.localStore.position === 'right';
206
+ const previousPaddings = (({ padding, paddingTop, paddingBottom, paddingLeft, paddingRight, }) => ({
207
+ padding,
208
+ paddingTop,
209
+ paddingBottom,
210
+ paddingLeft,
211
+ paddingRight,
212
+ }))(rootContainer.style);
213
+ rootContainer.style[styleProp] = `${isVertical ? props.localStore.width : props.localStore.height}px`;
214
+ onCleanup(() => {
215
+ Object.entries(previousPaddings).forEach(([property, previousValue]) => {
216
+ rootContainer.style[property] =
217
+ previousValue;
218
+ });
219
+ });
220
+ });
221
+ return (<aside
222
+ // Some context for styles here
223
+ // background-color - Changes to a lighter color create a harder contrast
224
+ // between the queries and query detail panel
225
+ // -
226
+ // min-width - When the panel is in the left or right position, the panel
227
+ // width is set to min-content to allow the panel to shrink to the lowest possible width
228
+ class={`${styles.panel} ${styles[`panel-position-${position()}`]} ${css `
229
+ flex-direction: ${panelWidth() < secondBreakpoint ? 'column' : 'row'};
230
+ background-color: ${panelWidth() < secondBreakpoint
231
+ ? tokens.colors.gray[600]
232
+ : tokens.colors.darkGray[900]};
233
+ ${panelWidth() < thirdBreakpoint &&
234
+ (position() === 'right' || position() === 'left')
235
+ ? `
236
+ min-width: min-content;
237
+ `
238
+ : ''}
239
+ `}`} style={{
240
+ height: position() === 'bottom' || position() === 'top'
241
+ ? `${props.localStore.height || DEFAULT_HEIGHT}px`
242
+ : 'auto',
243
+ width: position() === 'right' || position() === 'left'
244
+ ? `${props.localStore.width || DEFAULT_WIDTH}px`
245
+ : 'auto',
246
+ }} ref={panelRef} aria-label="Tanstack query devtools">
247
+ <div class={cx(styles.dragHandle, styles[`dragHandle-position-${position()}`])} onMouseDown={handleDragStart}></div>
248
+ <button aria-label="Close tanstack query devtools" class={cx(styles.closeBtn, styles[`closeBtn-position-${position()}`])} onClick={() => props.setLocalStore('open', 'false')}>
249
+ <ChevronDown />
250
+ </button>
251
+ <div ref={queriesContainerRef}
252
+ // When the panels are stacked we use the height style
253
+ // to divide the panels into two equal parts
254
+ class={`${styles.queriesContainer} ${css `
255
+ ${panelWidth() < secondBreakpoint && selectedQueryHash()
256
+ ? `
257
+ height: 50%;
258
+ max-height: 50%;
259
+ `
260
+ : ''}
261
+ `}`}>
262
+ <div class={cx(styles.row)}>
263
+ <button class={styles.logo} onClick={() => props.setLocalStore('open', 'false')} aria-label="Close Tanstack query devtools">
264
+ <span class={styles.tanstackLogo}>TANSTACK</span>
265
+ <span class={styles.queryFlavorLogo}>
266
+ {useQueryDevtoolsContext().queryFlavor} v
267
+ {useQueryDevtoolsContext().version}
268
+ </span>
269
+ </button>
270
+ <QueryStatusCount />
271
+ </div>
272
+ <div class={cx(styles.row, css `
273
+ gap: ${tokens.size[2.5]};
274
+ `)}>
275
+ <div class={styles.filtersContainer}>
276
+ <div class={styles.filterInput}>
277
+ <Search />
278
+ <input aria-label="Filter queries by query key" type="text" placeholder="Filter" onInput={(e) => props.setLocalStore('filter', e.currentTarget.value)} value={props.localStore.filter || ''}/>
279
+ </div>
280
+ <div class={styles.filterSelect}>
281
+ <select value={sort()} onChange={(e) => props.setLocalStore('sort', e.currentTarget.value)}>
282
+ {Object.keys(sortFns).map((key) => (<option value={key}>Sort by {key}</option>))}
283
+ </select>
284
+ <ChevronDown />
285
+ </div>
286
+ <button onClick={() => {
287
+ props.setLocalStore('sortOrder', String(sortOrder() * -1));
288
+ }} aria-label={`Sort order ${sortOrder() === -1 ? 'descending' : 'ascending'}`} aria-pressed={sortOrder() === -1}>
289
+ <Show when={sortOrder() === 1}>
290
+ <span>Asc</span>
291
+ <ArrowUp />
292
+ </Show>
293
+ <Show when={sortOrder() === -1}>
294
+ <span>Desc</span>
295
+ <ArrowDown />
296
+ </Show>
297
+ </button>
298
+ </div>
299
+
300
+ <div class={styles.actionsContainer}>
301
+ <button onClick={() => {
302
+ if (offline()) {
303
+ onlineManager().setOnline(undefined);
304
+ setOffline(false);
305
+ window.dispatchEvent(new Event('online'));
306
+ }
307
+ else {
308
+ onlineManager().setOnline(false);
309
+ setOffline(true);
310
+ }
311
+ }} class={styles.actionsBtn} aria-label={`${offline()
312
+ ? 'Unset offline mocking behavior'
313
+ : 'Mock offline behavior'}`} aria-pressed={offline()}>
314
+ {offline() ? <Offline /> : <Wifi />}
315
+ </button>
316
+ <div style={{ position: 'relative' }}>
317
+ <button onClick={() => setSettingsOpen((prev) => !prev)} class={styles.actionsBtn} id="TSQD-settings-menu-btn" aria-label={`${settingsOpen() ? 'Close' : 'Open'} settings menu`} aria-haspopup="true" aria-controls="TSQD-settings-menu">
318
+ <Settings />
319
+ </button>
320
+ <Show when={settingsOpen()}>
321
+ <div role="menu" tabindex="-1" aria-labelledby="TSQD-settings-menu-btn" id="TSQD-settings-menu" class={styles.settingsMenu}>
322
+ <div class={styles.settingsMenuHeader}>Position</div>
323
+ <div class={styles.settingsMenuSection}>
324
+ <button onClick={() => {
325
+ setDevtoolsPosition('top');
326
+ }} aria-label="Position top">
327
+ <ArrowUp />
328
+ <span>Top</span>
329
+ </button>
330
+ <button onClick={() => {
331
+ setDevtoolsPosition('bottom');
332
+ }} aria-label="Position bottom">
333
+ <ArrowDown />
334
+ <span>Bottom</span>
335
+ </button>
336
+ <button onClick={() => {
337
+ setDevtoolsPosition('left');
338
+ }} aria-label="Position left">
339
+ <ArrowDown />
340
+ <span>Left</span>
341
+ </button>
342
+ <button onClick={() => {
343
+ setDevtoolsPosition('right');
344
+ }} aria-label="Position right">
345
+ <ArrowDown />
346
+ <span>Right</span>
347
+ </button>
348
+ </div>
349
+ </div>
350
+ </Show>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ <div class={styles.overflowQueryContainer}>
355
+ <div>
356
+ <Key by={(q) => q.queryHash} each={queries()}>
357
+ {(query) => <QueryRow query={query()}/>}
358
+ </Key>
359
+ </div>
360
+ </div>
361
+ </div>
362
+ <Show when={selectedQueryHash()}>
363
+ <QueryDetails />
364
+ </Show>
365
+ </aside>);
366
+ };
367
+ export const QueryRow = (props) => {
368
+ const styles = getStyles();
369
+ const queryState = createSubscribeToQueryCacheBatcher((queryCache) => queryCache().find({
370
+ queryKey: props.query.queryKey,
371
+ })?.state);
372
+ const isDisabled = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
373
+ .find({
374
+ queryKey: props.query.queryKey,
375
+ })
376
+ ?.isDisabled() ?? false);
377
+ const isStale = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
378
+ .find({
379
+ queryKey: props.query.queryKey,
380
+ })
381
+ ?.isStale() ?? false);
382
+ const observers = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
383
+ .find({
384
+ queryKey: props.query.queryKey,
385
+ })
386
+ ?.getObserversCount() ?? 0);
387
+ const color = createMemo(() => getQueryStatusColor({
388
+ queryState: queryState(),
389
+ observerCount: observers(),
390
+ isStale: isStale(),
391
+ }));
392
+ return (<Show when={queryState()}>
393
+ <button onClick={() => setSelectedQueryHash(props.query.queryHash === selectedQueryHash()
394
+ ? null
395
+ : props.query.queryHash)} class={cx(styles.queryRow, selectedQueryHash() === props.query.queryHash &&
396
+ styles.selectedQueryRow)} aria-label={`Query key ${props.query.queryHash}`}>
397
+ <div class={cx('TSQDObserverCount', color() === 'gray'
398
+ ? css `
399
+ background-color: ${tokens.colors[color()][700]};
400
+ color: ${tokens.colors[color()][300]};
401
+ `
402
+ : css `
403
+ background-color: ${tokens.colors[color()][900]};
404
+ color: ${tokens.colors[color()][300]} !important;
405
+ `)}>
406
+ {observers()}
407
+ </div>
408
+ <code class="TSQDQueryHash">{props.query.queryHash}</code>
409
+ <Show when={isDisabled()}>
410
+ <div class="TSQDQueryDisabled">disabled</div>
411
+ </Show>
412
+ </button>
413
+ </Show>);
414
+ };
415
+ export const QueryStatusCount = () => {
416
+ const stale = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
417
+ .getAll()
418
+ .filter((q) => getQueryStatusLabel(q) === 'stale').length);
419
+ const fresh = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
420
+ .getAll()
421
+ .filter((q) => getQueryStatusLabel(q) === 'fresh').length);
422
+ const fetching = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
423
+ .getAll()
424
+ .filter((q) => getQueryStatusLabel(q) === 'fetching').length);
425
+ const paused = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
426
+ .getAll()
427
+ .filter((q) => getQueryStatusLabel(q) === 'paused').length);
428
+ const inactive = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
429
+ .getAll()
430
+ .filter((q) => getQueryStatusLabel(q) === 'inactive').length);
431
+ const styles = getStyles();
432
+ return (<div class={styles.queryStatusContainer}>
433
+ <QueryStatus label="Fresh" color="green" count={fresh()}/>
434
+ <QueryStatus label="Fetching" color="blue" count={fetching()}/>
435
+ <QueryStatus label="Paused" color="purple" count={paused()}/>
436
+ <QueryStatus label="Stale" color="yellow" count={stale()}/>
437
+ <QueryStatus label="Inactive" color="gray" count={inactive()}/>
438
+ </div>);
439
+ };
440
+ export const QueryStatus = (props) => {
441
+ const styles = getStyles();
442
+ let tagRef;
443
+ const [mouseOver, setMouseOver] = createSignal(false);
444
+ const [focused, setFocused] = createSignal(false);
445
+ const showLabel = createMemo(() => {
446
+ if (selectedQueryHash()) {
447
+ if (panelWidth() < firstBreakpoint && panelWidth() > secondBreakpoint) {
448
+ return false;
449
+ }
450
+ }
451
+ if (panelWidth() < thirdBreakpoint) {
452
+ return false;
453
+ }
454
+ return true;
455
+ });
456
+ return (<button onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} onMouseEnter={() => setMouseOver(true)} onMouseLeave={() => {
457
+ setMouseOver(false);
458
+ setFocused(false);
459
+ }} disabled={showLabel()} ref={tagRef} class={cx(styles.queryStatusTag, !showLabel()
460
+ ? css `
461
+ cursor: pointer;
462
+ &:hover {
463
+ background: ${tokens.colors.darkGray[400]}${tokens.alpha[80]};
464
+ }
465
+ `
466
+ : null)} {...(mouseOver() || focused()
467
+ ? {
468
+ 'aria-describedby': 'TSQD-status-tooltip',
469
+ }
470
+ : {})}>
471
+ <Show when={!showLabel() && (mouseOver() || focused())}>
472
+ <div role="tooltip" id="TSQD-status-tooltip" class={cx(styles.statusTooltip)}>
473
+ {props.label}
474
+ </div>
475
+ </Show>
476
+ <span class={css `
477
+ width: ${tokens.size[2]};
478
+ height: ${tokens.size[2]};
479
+ border-radius: ${tokens.border.radius.full};
480
+ background-color: ${tokens.colors[props.color][500]};
481
+ `}/>
482
+ <Show when={showLabel()}>
483
+ <span>{props.label}</span>
484
+ </Show>
485
+ <span class={cx(styles.queryStatusCount, props.count > 0 && props.color !== 'gray'
486
+ ? css `
487
+ background-color: ${tokens.colors[props.color][900]};
488
+ color: ${tokens.colors[props.color][300]} !important;
489
+ `
490
+ : css `
491
+ color: ${tokens.colors['gray'][400]} !important;
492
+ `)}>
493
+ {props.count}
494
+ </span>
495
+ </button>);
496
+ };
497
+ const QueryDetails = () => {
498
+ const styles = getStyles();
499
+ const queryClient = useQueryDevtoolsContext().client;
500
+ const [restoringLoading, setRestoringLoading] = createSignal(false);
501
+ const errorTypes = createMemo(() => {
502
+ return useQueryDevtoolsContext().errorTypes || [];
503
+ });
504
+ const activeQuery = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
505
+ .getAll()
506
+ .find((query) => query.queryHash === selectedQueryHash()), false);
507
+ const activeQueryFresh = createSubscribeToQueryCacheBatcher((queryCache) => {
508
+ return queryCache()
509
+ .getAll()
510
+ .find((query) => query.queryHash === selectedQueryHash());
511
+ }, false);
512
+ const activeQueryState = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
513
+ .getAll()
514
+ .find((query) => query.queryHash === selectedQueryHash())?.state, false);
515
+ const activeQueryStateData = createSubscribeToQueryCacheBatcher((queryCache) => {
516
+ return queryCache()
517
+ .getAll()
518
+ .find((query) => query.queryHash === selectedQueryHash())?.state.data;
519
+ }, false);
520
+ const statusLabel = createSubscribeToQueryCacheBatcher((queryCache) => {
521
+ const query = queryCache()
522
+ .getAll()
523
+ .find((q) => q.queryHash === selectedQueryHash());
524
+ if (!query)
525
+ return 'inactive';
526
+ return getQueryStatusLabel(query);
527
+ });
528
+ const queryStatus = createSubscribeToQueryCacheBatcher((queryCache) => {
529
+ const query = queryCache()
530
+ .getAll()
531
+ .find((q) => q.queryHash === selectedQueryHash());
532
+ if (!query)
533
+ return 'pending';
534
+ return query.state.status;
535
+ });
536
+ const observerCount = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
537
+ .getAll()
538
+ .find((query) => query.queryHash === selectedQueryHash())
539
+ ?.getObserversCount() ?? 0);
540
+ const color = createMemo(() => getQueryStatusColorByLabel(statusLabel()));
541
+ const handleRefetch = () => {
542
+ const promise = activeQuery()?.fetch();
543
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
544
+ promise?.catch(() => { });
545
+ };
546
+ const triggerError = (errorType) => {
547
+ const error = errorType?.initializer(activeQuery()) ??
548
+ new Error('Unknown error from devtools');
549
+ const __previousQueryOptions = activeQuery().options;
550
+ activeQuery().setState({
551
+ status: 'error',
552
+ error,
553
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
554
+ fetchMeta: {
555
+ ...activeQuery().state.fetchMeta,
556
+ __previousQueryOptions,
557
+ },
558
+ });
559
+ };
560
+ const restoreQueryAfterLoadingOrError = () => {
561
+ activeQuery()?.fetch((activeQuery()?.state.fetchMeta).__previousQueryOptions, {
562
+ // Make sure this fetch will cancel the previous one
563
+ cancelRefetch: true,
564
+ });
565
+ };
566
+ createEffect(() => {
567
+ if (statusLabel() !== 'fetching') {
568
+ setRestoringLoading(false);
569
+ }
570
+ });
571
+ return (<Show when={activeQuery() && activeQueryState()}>
572
+ <div class={styles.detailsContainer}>
573
+ <div class={styles.detailsHeader}>Query Details</div>
574
+ <div class={styles.detailsBody}>
575
+ <div>
576
+ <pre>
577
+ <code>{displayValue(activeQuery().queryKey, true)}</code>
578
+ </pre>
579
+ <span class={cx(styles.queryDetailsStatus, color() === 'gray'
580
+ ? css `
581
+ background-color: ${tokens.colors[color()][700]};
582
+ color: ${tokens.colors[color()][300]};
583
+ border-color: ${tokens.colors[color()][600]};
584
+ `
585
+ : css `
586
+ background-color: ${tokens.colors[color()][900]};
587
+ color: ${tokens.colors[color()][300]};
588
+ border-color: ${tokens.colors[color()][600]};
589
+ `)}>
590
+ {statusLabel()}
591
+ </span>
592
+ </div>
593
+ <div>
594
+ <span>Observers:</span>
595
+ <span>{observerCount()}</span>
596
+ </div>
597
+ <div>
598
+ <span>Last Updated:</span>
599
+ <span>
600
+ {new Date(activeQueryState().dataUpdatedAt).toLocaleTimeString()}
601
+ </span>
602
+ </div>
603
+ </div>
604
+ <div class={styles.detailsHeader}>Actions</div>
605
+ <div class={styles.actionsBody}>
606
+ <button class={css `
607
+ color: ${tokens.colors.blue[400]};
608
+ `} onClick={handleRefetch} disabled={statusLabel() === 'fetching'}>
609
+ <span class={css `
610
+ background-color: ${tokens.colors.blue[400]};
611
+ `}></span>
612
+ Refetch
613
+ </button>
614
+ <button class={css `
615
+ color: ${tokens.colors.yellow[400]};
616
+ `} onClick={() => queryClient.invalidateQueries(activeQuery())}>
617
+ <span class={css `
618
+ background-color: ${tokens.colors.yellow[400]};
619
+ `}></span>
620
+ Invalidate
621
+ </button>
622
+ <button class={css `
623
+ color: ${tokens.colors.gray[300]};
624
+ `} onClick={() => queryClient.resetQueries(activeQuery())}>
625
+ <span class={css `
626
+ background-color: ${tokens.colors.gray[400]};
627
+ `}></span>
628
+ Reset
629
+ </button>
630
+ <button class={css `
631
+ color: ${tokens.colors.cyan[400]};
632
+ `} disabled={restoringLoading()} onClick={() => {
633
+ if (activeQuery()?.state.data === undefined) {
634
+ setRestoringLoading(true);
635
+ restoreQueryAfterLoadingOrError();
636
+ }
637
+ else {
638
+ const activeQueryVal = activeQuery();
639
+ if (!activeQueryVal)
640
+ return;
641
+ const __previousQueryOptions = activeQueryVal.options;
642
+ // Trigger a fetch in order to trigger suspense as well.
643
+ activeQueryVal.fetch({
644
+ ...__previousQueryOptions,
645
+ queryFn: () => {
646
+ return new Promise(() => {
647
+ // Never resolve
648
+ });
649
+ },
650
+ gcTime: -1,
651
+ });
652
+ activeQueryVal.setState({
653
+ data: undefined,
654
+ status: 'pending',
655
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
656
+ fetchMeta: {
657
+ ...activeQueryVal.state.fetchMeta,
658
+ __previousQueryOptions,
659
+ },
660
+ });
661
+ }
662
+ }}>
663
+ <span class={css `
664
+ background-color: ${tokens.colors.cyan[400]};
665
+ `}></span>
666
+ {statusLabel() === 'fetching' ? 'Restore' : 'Trigger'} Loading
667
+ </button>
668
+ <Show when={errorTypes().length === 0 || queryStatus() === 'error'}>
669
+ <button class={css `
670
+ color: ${tokens.colors.red[400]};
671
+ `} onClick={() => {
672
+ if (!activeQuery().state.error) {
673
+ triggerError();
674
+ }
675
+ else {
676
+ queryClient.resetQueries(activeQuery());
677
+ }
678
+ }}>
679
+ <span class={css `
680
+ background-color: ${tokens.colors.red[400]};
681
+ `}></span>
682
+ {queryStatus() === 'error' ? 'Restore' : 'Trigger'} Error
683
+ </button>
684
+ </Show>
685
+ <Show when={!(errorTypes().length === 0 || queryStatus() === 'error')}>
686
+ <div class={styles.actionsSelect}>
687
+ <span class={css `
688
+ background-color: ${tokens.colors.red[400]};
689
+ `}></span>
690
+ Trigger Error
691
+ <select disabled={queryStatus() === 'pending'} onChange={(e) => {
692
+ const errorType = errorTypes().find((t) => t.name === e.currentTarget.value);
693
+ triggerError(errorType);
694
+ }}>
695
+ <option value="" disabled selected></option>
696
+ <For each={errorTypes()}>
697
+ {(errorType) => (<option value={errorType.name}>{errorType.name}</option>)}
698
+ </For>
699
+ </select>
700
+ <ChevronDown />
701
+ </div>
702
+ </Show>
703
+ </div>
704
+ <div class={styles.detailsHeader}>Data Explorer</div>
705
+ <div style={{
706
+ padding: '0.5rem',
707
+ }}>
708
+ <Explorer label="Data" defaultExpanded={['Data']} value={activeQueryStateData()} copyable={true}/>
709
+ </div>
710
+ <div class={styles.detailsHeader}>Query Explorer</div>
711
+ <div style={{
712
+ padding: '0.5rem',
713
+ }}>
714
+ <Explorer label="Query" defaultExpanded={['Query', 'queryKey']} value={activeQueryFresh()}/>
715
+ </div>
716
+ </div>
717
+ </Show>);
718
+ };
719
+ const signalsMap = new Map();
720
+ const setupQueryCacheSubscription = () => {
721
+ const queryCache = createMemo(() => {
722
+ const client = useQueryDevtoolsContext().client;
723
+ return client.getQueryCache();
724
+ });
725
+ const unsub = queryCache().subscribe(() => {
726
+ for (const [callback, setter] of signalsMap.entries()) {
727
+ queueMicrotask(() => {
728
+ setter(callback(queryCache));
729
+ });
730
+ }
731
+ });
732
+ onCleanup(() => {
733
+ signalsMap.clear();
734
+ unsub();
735
+ });
736
+ return unsub;
737
+ };
738
+ const createSubscribeToQueryCacheBatcher = (callback, equalityCheck = true) => {
739
+ const queryCache = createMemo(() => {
740
+ const client = useQueryDevtoolsContext().client;
741
+ return client.getQueryCache();
742
+ });
743
+ const [value, setValue] = createSignal(callback(queryCache), !equalityCheck ? { equals: false } : undefined);
744
+ createEffect(() => {
745
+ setValue(callback(queryCache));
746
+ });
747
+ // @ts-ignore
748
+ signalsMap.set(callback, setValue);
749
+ onCleanup(() => {
750
+ // @ts-ignore
751
+ signalsMap.delete(callback);
752
+ });
753
+ return value;
754
+ };
755
+ const getStyles = () => {
756
+ const { colors, font, size, alpha, shadow, border } = tokens;
757
+ return {
758
+ devtoolsBtn: css `
759
+ z-index: 100000;
760
+ position: fixed;
761
+ padding: 4px;
762
+
763
+ display: flex;
764
+ align-items: center;
765
+ justify-content: center;
766
+ border-radius: 9999px;
767
+ box-shadow: ${shadow.md()};
768
+ overflow: hidden;
769
+
770
+ & div {
771
+ position: absolute;
772
+ top: -8px;
773
+ left: -8px;
774
+ right: -8px;
775
+ bottom: -8px;
776
+ border-radius: 9999px;
777
+
778
+ & svg {
779
+ position: absolute;
780
+ width: 100%;
781
+ height: 100%;
782
+ }
783
+ filter: blur(6px) saturate(1.2) contrast(1.1);
784
+ }
785
+
786
+ &:focus-within {
787
+ outline-offset: 2px;
788
+ outline: 3px solid ${colors.green[600]};
789
+ }
790
+
791
+ & button {
792
+ position: relative;
793
+ z-index: 1;
794
+ padding: 0;
795
+ border-radius: 9999px;
796
+ background-color: transparent;
797
+ border: none;
798
+ height: 40px;
799
+ display: flex;
800
+ width: 40px;
801
+ overflow: hidden;
802
+ cursor: pointer;
803
+ outline: none;
804
+ & svg {
805
+ position: absolute;
806
+ width: 100%;
807
+ height: 100%;
808
+ }
809
+ }
810
+ `,
811
+ panel: css `
812
+ position: fixed;
813
+ z-index: 9999;
814
+ display: flex;
815
+ gap: ${tokens.size[0.5]};
816
+ & * {
817
+ font-family: 'Inter', sans-serif;
818
+ color: ${colors.gray[300]};
819
+ box-sizing: border-box;
820
+ text-transform: none;
821
+ }
822
+ `,
823
+ 'devtoolsBtn-position-bottom-right': css `
824
+ bottom: 12px;
825
+ right: 12px;
826
+ `,
827
+ 'devtoolsBtn-position-bottom-left': css `
828
+ bottom: 12px;
829
+ left: 12px;
830
+ `,
831
+ 'devtoolsBtn-position-top-left': css `
832
+ top: 12px;
833
+ left: 12px;
834
+ `,
835
+ 'devtoolsBtn-position-top-right': css `
836
+ top: 12px;
837
+ right: 12px;
838
+ `,
839
+ 'panel-position-top': css `
840
+ top: 0;
841
+ right: 0;
842
+ left: 0;
843
+ max-height: 90%;
844
+ min-height: 3.5rem;
845
+ border-bottom: ${colors.darkGray[300]} 1px solid;
846
+ `,
847
+ 'panel-position-bottom': css `
848
+ bottom: 0;
849
+ right: 0;
850
+ left: 0;
851
+ max-height: 90%;
852
+ min-height: 3.5rem;
853
+ border-top: ${colors.darkGray[300]} 1px solid;
854
+ `,
855
+ 'panel-position-right': css `
856
+ bottom: 0;
857
+ right: 0;
858
+ top: 0;
859
+ border-left: ${colors.darkGray[300]} 1px solid;
860
+ max-width: 90%;
861
+ `,
862
+ 'panel-position-left': css `
863
+ bottom: 0;
864
+ left: 0;
865
+ top: 0;
866
+ border-right: ${colors.darkGray[300]} 1px solid;
867
+ max-width: 90%;
868
+ `,
869
+ closeBtn: css `
870
+ position: absolute;
871
+ cursor: pointer;
872
+ z-index: 5;
873
+ display: flex;
874
+ align-items: center;
875
+ justify-content: center;
876
+ outline: none;
877
+ &:hover {
878
+ background-color: ${colors.darkGray[500]};
879
+ }
880
+ &:focus-visible {
881
+ outline: 2px solid ${colors.blue[600]};
882
+ }
883
+ `,
884
+ 'closeBtn-position-top': css `
885
+ bottom: 0;
886
+ right: ${size[3]};
887
+ transform: translate(0, 100%);
888
+ background-color: ${colors.darkGray[700]};
889
+ border-right: ${colors.darkGray[300]} 1px solid;
890
+ border-left: ${colors.darkGray[300]} 1px solid;
891
+ border-top: none;
892
+ border-bottom: ${colors.darkGray[300]} 1px solid;
893
+ border-radius: 0px 0px ${border.radius.sm} ${border.radius.sm};
894
+ padding: ${size[1.5]} ${size[2.5]} ${size[2]} ${size[2.5]};
895
+
896
+ &::after {
897
+ content: ' ';
898
+ position: absolute;
899
+ bottom: 100%;
900
+ left: -${size[2.5]};
901
+ height: ${size[1.5]};
902
+ width: calc(100% + ${size[5]});
903
+ }
904
+
905
+ & svg {
906
+ transform: rotate(180deg);
907
+ }
908
+ `,
909
+ 'closeBtn-position-bottom': css `
910
+ top: 0;
911
+ right: ${size[3]};
912
+ transform: translate(0, -100%);
913
+ background-color: ${colors.darkGray[700]};
914
+ border-right: ${colors.darkGray[300]} 1px solid;
915
+ border-left: ${colors.darkGray[300]} 1px solid;
916
+ border-top: ${colors.darkGray[300]} 1px solid;
917
+ border-bottom: none;
918
+ border-radius: ${border.radius.sm} ${border.radius.sm} 0px 0px;
919
+ padding: ${size[2]} ${size[2.5]} ${size[1.5]} ${size[2.5]};
920
+
921
+ &::after {
922
+ content: ' ';
923
+ position: absolute;
924
+ top: 100%;
925
+ left: -${size[2.5]};
926
+ height: ${size[1.5]};
927
+ width: calc(100% + ${size[5]});
928
+ }
929
+ `,
930
+ 'closeBtn-position-right': css `
931
+ bottom: ${size[3]};
932
+ left: 0;
933
+ transform: translate(-100%, 0);
934
+ background-color: ${colors.darkGray[700]};
935
+ border-right: none;
936
+ border-left: ${colors.darkGray[300]} 1px solid;
937
+ border-top: ${colors.darkGray[300]} 1px solid;
938
+ border-bottom: ${colors.darkGray[300]} 1px solid;
939
+ border-radius: ${border.radius.sm} 0px 0px ${border.radius.sm};
940
+ padding: ${size[2.5]} ${size[1]} ${size[2.5]} ${size[1.5]};
941
+
942
+ &::after {
943
+ content: ' ';
944
+ position: absolute;
945
+ left: 100%;
946
+ height: calc(100% + ${size[5]});
947
+ width: ${size[1.5]};
948
+ }
949
+
950
+ & svg {
951
+ transform: rotate(-90deg);
952
+ }
953
+ `,
954
+ 'closeBtn-position-left': css `
955
+ bottom: ${size[3]};
956
+ right: 0;
957
+ transform: translate(100%, 0);
958
+ background-color: ${colors.darkGray[700]};
959
+ border-left: none;
960
+ border-right: ${colors.darkGray[300]} 1px solid;
961
+ border-top: ${colors.darkGray[300]} 1px solid;
962
+ border-bottom: ${colors.darkGray[300]} 1px solid;
963
+ border-radius: 0px ${border.radius.sm} ${border.radius.sm} 0px;
964
+ padding: ${size[2.5]} ${size[1.5]} ${size[2.5]} ${size[1]};
965
+
966
+ &::after {
967
+ content: ' ';
968
+ position: absolute;
969
+ right: 100%;
970
+ height: calc(100% + ${size[5]});
971
+ width: ${size[1.5]};
972
+ }
973
+
974
+ & svg {
975
+ transform: rotate(90deg);
976
+ }
977
+ `,
978
+ queriesContainer: css `
979
+ flex: 1 1 700px;
980
+ background-color: ${colors.darkGray[700]};
981
+ display: flex;
982
+ flex-direction: column;
983
+ `,
984
+ dragHandle: css `
985
+ position: absolute;
986
+ transition: background-color 0.125s ease;
987
+ &:hover {
988
+ background-color: ${colors.gray[400]}${alpha[90]};
989
+ }
990
+ z-index: 4;
991
+ `,
992
+ 'dragHandle-position-top': css `
993
+ bottom: 0;
994
+ width: 100%;
995
+ height: ${tokens.size[1]};
996
+ cursor: ns-resize;
997
+ `,
998
+ 'dragHandle-position-bottom': css `
999
+ top: 0;
1000
+ width: 100%;
1001
+ height: ${tokens.size[1]};
1002
+ cursor: ns-resize;
1003
+ `,
1004
+ 'dragHandle-position-right': css `
1005
+ left: 0;
1006
+ width: ${tokens.size[1]};
1007
+ height: 100%;
1008
+ cursor: ew-resize;
1009
+ `,
1010
+ 'dragHandle-position-left': css `
1011
+ right: 0;
1012
+ width: ${tokens.size[1]};
1013
+ height: 100%;
1014
+ cursor: ew-resize;
1015
+ `,
1016
+ row: css `
1017
+ display: flex;
1018
+ justify-content: space-between;
1019
+ padding: ${tokens.size[2.5]} ${tokens.size[3]};
1020
+ gap: ${tokens.size[4]};
1021
+ border-bottom: ${colors.darkGray[500]} 1px solid;
1022
+ align-items: center;
1023
+ & > button {
1024
+ padding: 0;
1025
+ background: transparent;
1026
+ border: none;
1027
+ display: flex;
1028
+ flex-direction: column;
1029
+ }
1030
+ `,
1031
+ logo: css `
1032
+ cursor: pointer;
1033
+ &:hover {
1034
+ opacity: 0.7;
1035
+ }
1036
+ &:focus-visible {
1037
+ outline-offset: 4px;
1038
+ border-radius: ${border.radius.xs};
1039
+ outline: 2px solid ${colors.blue[800]};
1040
+ }
1041
+ `,
1042
+ tanstackLogo: css `
1043
+ font-size: ${font.size.lg};
1044
+ font-weight: ${font.weight.extrabold};
1045
+ line-height: ${font.lineHeight.sm};
1046
+ white-space: nowrap;
1047
+ `,
1048
+ queryFlavorLogo: css `
1049
+ font-weight: ${font.weight.semibold};
1050
+ font-size: ${font.size.sm};
1051
+ background: linear-gradient(to right, #dd524b, #e9a03b);
1052
+ background-clip: text;
1053
+ line-height: ${font.lineHeight.xs};
1054
+ -webkit-text-fill-color: transparent;
1055
+ white-space: nowrap;
1056
+ `,
1057
+ queryStatusContainer: css `
1058
+ display: flex;
1059
+ gap: ${tokens.size[2]};
1060
+ height: min-content;
1061
+ `,
1062
+ queryStatusTag: css `
1063
+ display: flex;
1064
+ gap: ${tokens.size[1.5]};
1065
+ background: ${colors.darkGray[500]};
1066
+ border-radius: ${tokens.border.radius.md};
1067
+ font-size: ${font.size.sm};
1068
+ padding: ${tokens.size[1]};
1069
+ padding-left: ${tokens.size[2.5]};
1070
+ align-items: center;
1071
+ line-height: ${font.lineHeight.md};
1072
+ font-weight: ${font.weight.medium};
1073
+ border: none;
1074
+ user-select: none;
1075
+ position: relative;
1076
+ &:focus-visible {
1077
+ outline-offset: 2px;
1078
+ outline: 2px solid ${colors.blue[800]};
1079
+ }
1080
+ & span:nth-child(2) {
1081
+ color: ${colors.gray[300]}${alpha[80]};
1082
+ }
1083
+ `,
1084
+ statusTooltip: css `
1085
+ position: absolute;
1086
+ z-index: 1;
1087
+ background-color: ${colors.darkGray[500]};
1088
+ top: 100%;
1089
+ left: 50%;
1090
+ transform: translate(-50%, calc(${tokens.size[2]}));
1091
+ padding: ${tokens.size[0.5]} ${tokens.size[3]};
1092
+ border-radius: ${tokens.border.radius.md};
1093
+ font-size: ${font.size.sm};
1094
+ border: 2px solid ${colors.gray[600]};
1095
+ color: ${tokens.colors['gray'][300]};
1096
+
1097
+ &::before {
1098
+ top: 0px;
1099
+ content: ' ';
1100
+ display: block;
1101
+ left: 50%;
1102
+ transform: translate(-50%, -100%);
1103
+ position: absolute;
1104
+ border-color: transparent transparent ${colors.gray[600]} transparent;
1105
+ border-style: solid;
1106
+ border-width: 7px;
1107
+ /* transform: rotate(180deg); */
1108
+ }
1109
+
1110
+ &::after {
1111
+ top: 0px;
1112
+ content: ' ';
1113
+ display: block;
1114
+ left: 50%;
1115
+ transform: translate(-50%, calc(-100% + 2.5px));
1116
+ position: absolute;
1117
+ border-color: transparent transparent ${colors.darkGray[500]}
1118
+ transparent;
1119
+ border-style: solid;
1120
+ border-width: 7px;
1121
+ }
1122
+ `,
1123
+ selectedQueryRow: css `
1124
+ background-color: ${colors.darkGray[500]};
1125
+ `,
1126
+ queryStatusCount: css `
1127
+ padding: 0 8px;
1128
+ display: flex;
1129
+ align-items: center;
1130
+ justify-content: center;
1131
+ color: ${colors.gray[400]};
1132
+ background-color: ${colors.darkGray[300]};
1133
+ border-radius: 3px;
1134
+ font-variant-numeric: tabular-nums;
1135
+ `,
1136
+ filtersContainer: css `
1137
+ display: flex;
1138
+ gap: ${tokens.size[2.5]};
1139
+ & > button {
1140
+ cursor: pointer;
1141
+ padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1142
+ padding-right: ${tokens.size[1.5]};
1143
+ border-radius: ${tokens.border.radius.md};
1144
+ background-color: ${colors.darkGray[400]};
1145
+ font-size: ${font.size.sm};
1146
+ display: flex;
1147
+ align-items: center;
1148
+ line-height: ${font.lineHeight.sm};
1149
+ gap: ${tokens.size[1.5]};
1150
+ max-width: 160px;
1151
+ border: 1px solid ${colors.darkGray[200]};
1152
+ &:focus-visible {
1153
+ outline-offset: 2px;
1154
+ border-radius: ${border.radius.xs};
1155
+ outline: 2px solid ${colors.blue[800]};
1156
+ }
1157
+ }
1158
+ `,
1159
+ filterInput: css `
1160
+ padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1161
+ border-radius: ${tokens.border.radius.md};
1162
+ background-color: ${colors.darkGray[400]};
1163
+ display: flex;
1164
+ box-sizing: content-box;
1165
+ align-items: center;
1166
+ gap: ${tokens.size[1.5]};
1167
+ max-width: 160px;
1168
+ min-width: 100px;
1169
+ border: 1px solid ${colors.darkGray[200]};
1170
+ height: min-content;
1171
+ & > svg {
1172
+ width: ${tokens.size[3.5]};
1173
+ height: ${tokens.size[3.5]};
1174
+ }
1175
+ & input {
1176
+ font-size: ${font.size.sm};
1177
+ width: 100%;
1178
+ background-color: ${colors.darkGray[400]};
1179
+ border: none;
1180
+ padding: 0;
1181
+ line-height: ${font.lineHeight.sm};
1182
+ color: ${colors.gray[300]};
1183
+ &::placeholder {
1184
+ color: ${colors.gray[300]};
1185
+ }
1186
+ &:focus {
1187
+ outline: none;
1188
+ }
1189
+ }
1190
+
1191
+ &:focus-within {
1192
+ outline-offset: 2px;
1193
+ border-radius: ${border.radius.xs};
1194
+ outline: 2px solid ${colors.blue[800]};
1195
+ }
1196
+ `,
1197
+ filterSelect: css `
1198
+ padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1199
+ border-radius: ${tokens.border.radius.md};
1200
+ background-color: ${colors.darkGray[400]};
1201
+ display: flex;
1202
+ align-items: center;
1203
+ gap: ${tokens.size[1.5]};
1204
+ box-sizing: content-box;
1205
+ max-width: 160px;
1206
+ border: 1px solid ${colors.darkGray[200]};
1207
+ height: min-content;
1208
+ & > svg {
1209
+ width: ${tokens.size[3]};
1210
+ height: ${tokens.size[3]};
1211
+ }
1212
+ & > select {
1213
+ appearance: none;
1214
+ min-width: 100px;
1215
+ line-height: ${font.lineHeight.sm};
1216
+ font-size: ${font.size.sm};
1217
+ background-color: ${colors.darkGray[400]};
1218
+ border: none;
1219
+ &:focus {
1220
+ outline: none;
1221
+ }
1222
+ }
1223
+ &:focus-within {
1224
+ outline-offset: 2px;
1225
+ border-radius: ${border.radius.xs};
1226
+ outline: 2px solid ${colors.blue[800]};
1227
+ }
1228
+ `,
1229
+ actionsContainer: css `
1230
+ display: flex;
1231
+ gap: ${tokens.size[2.5]};
1232
+ `,
1233
+ actionsBtn: css `
1234
+ border-radius: ${tokens.border.radius.md};
1235
+ background-color: ${colors.darkGray[400]};
1236
+ width: 2.125rem; // 34px
1237
+ height: 2.125rem; // 34px
1238
+ justify-content: center;
1239
+ display: flex;
1240
+ align-items: center;
1241
+ gap: ${tokens.size[1.5]};
1242
+ max-width: 160px;
1243
+ border: 1px solid ${colors.darkGray[200]};
1244
+ cursor: pointer;
1245
+ &:hover {
1246
+ background-color: ${colors.darkGray[500]};
1247
+ }
1248
+ & svg {
1249
+ width: ${tokens.size[4]};
1250
+ height: ${tokens.size[4]};
1251
+ }
1252
+ &:focus-visible {
1253
+ outline-offset: 2px;
1254
+ border-radius: ${border.radius.xs};
1255
+ outline: 2px solid ${colors.blue[800]};
1256
+ }
1257
+ `,
1258
+ overflowQueryContainer: css `
1259
+ flex: 1;
1260
+ overflow-y: auto;
1261
+ & > div {
1262
+ display: flex;
1263
+ flex-direction: column;
1264
+ }
1265
+ `,
1266
+ queryRow: css `
1267
+ display: flex;
1268
+ align-items: center;
1269
+ padding: 0;
1270
+ background-color: inherit;
1271
+ border: none;
1272
+ cursor: pointer;
1273
+ &:focus-visible {
1274
+ outline-offset: -2px;
1275
+ border-radius: ${border.radius.xs};
1276
+ outline: 2px solid ${colors.blue[800]};
1277
+ }
1278
+ &:hover .TSQDQueryHash {
1279
+ background-color: ${colors.darkGray[600]};
1280
+ }
1281
+
1282
+ & .TSQDObserverCount {
1283
+ padding: 0 ${tokens.size[1]};
1284
+ user-select: none;
1285
+ min-width: ${tokens.size[8]};
1286
+ align-self: stretch !important;
1287
+ display: flex;
1288
+ align-items: center;
1289
+ justify-content: center;
1290
+ font-size: ${font.size.sm};
1291
+ font-weight: ${font.weight.medium};
1292
+ border-bottom: 1px solid ${colors.darkGray[700]};
1293
+ }
1294
+ & .TSQDQueryHash {
1295
+ user-select: text;
1296
+ font-size: ${font.size.sm};
1297
+ display: flex;
1298
+ align-items: center;
1299
+ min-height: ${tokens.size[8]};
1300
+ flex: 1;
1301
+ padding: ${tokens.size[1]} ${tokens.size[2]};
1302
+ font-family: 'Menlo', 'Fira Code', monospace !important;
1303
+ border-bottom: 1px solid ${colors.darkGray[400]};
1304
+ text-align: left;
1305
+ text-overflow: clip;
1306
+ word-break: break-word;
1307
+ }
1308
+
1309
+ & .TSQDQueryDisabled {
1310
+ align-self: stretch;
1311
+ align-self: stretch !important;
1312
+ display: flex;
1313
+ align-items: center;
1314
+ padding: 0 ${tokens.size[3]};
1315
+ color: ${colors.gray[300]};
1316
+ background-color: ${colors.darkGray[600]};
1317
+ border-bottom: 1px solid ${colors.darkGray[400]};
1318
+ font-size: ${font.size.sm};
1319
+ }
1320
+ `,
1321
+ detailsContainer: css `
1322
+ flex: 1 1 700px;
1323
+ background-color: ${colors.darkGray[700]};
1324
+ display: flex;
1325
+ flex-direction: column;
1326
+ overflow-y: auto;
1327
+ display: flex;
1328
+ `,
1329
+ detailsHeader: css `
1330
+ position: sticky;
1331
+ top: 0;
1332
+ z-index: 2;
1333
+ background-color: ${colors.darkGray[600]};
1334
+ padding: ${tokens.size[2]} ${tokens.size[2]};
1335
+ font-weight: ${font.weight.medium};
1336
+ font-size: ${font.size.sm};
1337
+ `,
1338
+ detailsBody: css `
1339
+ margin: ${tokens.size[2]} 0px ${tokens.size[3]} 0px;
1340
+ & > div {
1341
+ display: flex;
1342
+ align-items: stretch;
1343
+ padding: 0 ${tokens.size[2]};
1344
+ line-height: ${font.lineHeight.sm};
1345
+ justify-content: space-between;
1346
+ & > span {
1347
+ font-size: ${font.size.sm};
1348
+ }
1349
+ & > span:nth-child(2) {
1350
+ font-variant-numeric: tabular-nums;
1351
+ }
1352
+ }
1353
+
1354
+ & > div:first-child {
1355
+ margin-bottom: ${tokens.size[2]};
1356
+ }
1357
+
1358
+ & code {
1359
+ font-family: 'Menlo', 'Fira Code', monospace !important;
1360
+ margin: 0;
1361
+ font-size: ${font.size.sm};
1362
+ line-height: ${font.lineHeight.sm};
1363
+ }
1364
+ `,
1365
+ queryDetailsStatus: css `
1366
+ border: 1px solid ${colors.darkGray[200]};
1367
+ border-radius: ${tokens.border.radius.md};
1368
+ font-weight: ${font.weight.medium};
1369
+ padding: ${tokens.size[1]} ${tokens.size[2.5]};
1370
+ `,
1371
+ actionsBody: css `
1372
+ flex-wrap: wrap;
1373
+ margin: ${tokens.size[3]} 0px ${tokens.size[3]} 0px;
1374
+ display: flex;
1375
+ gap: ${tokens.size[2]};
1376
+ padding: 0px ${tokens.size[2]};
1377
+ & > button {
1378
+ font-size: ${font.size.sm};
1379
+ padding: ${tokens.size[2]} ${tokens.size[2]};
1380
+ display: flex;
1381
+ border-radius: ${tokens.border.radius.md};
1382
+ border: 1px solid ${colors.darkGray[400]};
1383
+ background-color: ${colors.darkGray[600]};
1384
+ align-items: center;
1385
+ gap: ${tokens.size[2]};
1386
+ font-weight: ${font.weight.medium};
1387
+ line-height: ${font.lineHeight.sm};
1388
+ cursor: pointer;
1389
+ &:focus-visible {
1390
+ outline-offset: 2px;
1391
+ border-radius: ${border.radius.xs};
1392
+ outline: 2px solid ${colors.blue[800]};
1393
+ }
1394
+ &:hover {
1395
+ background-color: ${colors.darkGray[500]};
1396
+ }
1397
+
1398
+ &:disabled {
1399
+ opacity: 0.6;
1400
+ cursor: not-allowed;
1401
+ }
1402
+
1403
+ & > span {
1404
+ width: ${size[2]};
1405
+ height: ${size[2]};
1406
+ border-radius: ${tokens.border.radius.full};
1407
+ }
1408
+ }
1409
+ `,
1410
+ actionsSelect: css `
1411
+ font-size: ${font.size.sm};
1412
+ padding: ${tokens.size[2]} ${tokens.size[2]};
1413
+ display: flex;
1414
+ border-radius: ${tokens.border.radius.md};
1415
+ overflow: hidden;
1416
+ border: 1px solid ${colors.darkGray[400]};
1417
+ background-color: ${colors.darkGray[600]};
1418
+ align-items: center;
1419
+ gap: ${tokens.size[2]};
1420
+ font-weight: ${font.weight.medium};
1421
+ line-height: ${font.lineHeight.sm};
1422
+ color: ${tokens.colors.red[400]};
1423
+ cursor: pointer;
1424
+ position: relative;
1425
+ &:hover {
1426
+ background-color: ${colors.darkGray[500]};
1427
+ }
1428
+ & > span {
1429
+ width: ${size[2]};
1430
+ height: ${size[2]};
1431
+ border-radius: ${tokens.border.radius.full};
1432
+ }
1433
+ &:focus-within {
1434
+ outline-offset: 2px;
1435
+ border-radius: ${border.radius.xs};
1436
+ outline: 2px solid ${colors.blue[800]};
1437
+ }
1438
+ & select {
1439
+ position: absolute;
1440
+ top: 0;
1441
+ left: 0;
1442
+ width: 100%;
1443
+ height: 100%;
1444
+ appearance: none;
1445
+ background-color: transparent;
1446
+ border: none;
1447
+ color: transparent;
1448
+ outline: none;
1449
+ }
1450
+
1451
+ & svg path {
1452
+ stroke: ${tokens.colors.red[400]} !important;
1453
+ }
1454
+ `,
1455
+ settingsMenu: css `
1456
+ position: absolute;
1457
+ top: calc(100% + ${tokens.size[2]});
1458
+ border-radius: ${tokens.border.radius.lg};
1459
+ border: 1px solid ${colors.gray[600]};
1460
+ right: 0;
1461
+ min-width: ${tokens.size[44]};
1462
+ background-color: ${colors.darkGray[400]};
1463
+ font-size: ${font.size.sm};
1464
+ color: ${colors.gray[500]};
1465
+ z-index: 7;
1466
+ `,
1467
+ settingsMenuHeader: css `
1468
+ padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1469
+ color: ${colors.gray[300]};
1470
+ font-weight: ${font.weight.medium};
1471
+ `,
1472
+ settingsMenuSection: css `
1473
+ border-top: 1px solid ${colors.gray[600]};
1474
+ display: flex;
1475
+ flex-direction: column;
1476
+ padding: ${tokens.size[1]} ${tokens.size[1]};
1477
+
1478
+ & > button {
1479
+ cursor: pointer;
1480
+ background-color: transparent;
1481
+ border: none;
1482
+ padding: ${tokens.size[2]} ${tokens.size[1.5]};
1483
+ font-size: ${font.size.sm};
1484
+ display: flex;
1485
+ align-items: center;
1486
+ justify-content: flex-start;
1487
+ gap: ${tokens.size[2]};
1488
+ border-radius: ${tokens.border.radius.md};
1489
+ &:hover {
1490
+ background-color: ${colors.darkGray[500]};
1491
+ }
1492
+
1493
+ &:focus-visible {
1494
+ outline-offset: 2px;
1495
+ outline: 2px solid ${colors.blue[800]};
1496
+ }
1497
+ }
1498
+
1499
+ & button:nth-child(4) svg {
1500
+ transform: rotate(-90deg);
1501
+ }
1502
+
1503
+ & button:nth-child(3) svg {
1504
+ transform: rotate(90deg);
1505
+ }
1506
+ `,
1507
+ };
1508
+ };