@tanstack/query-devtools 5.0.0-rc.1 → 5.0.0-rc.13
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.
- package/build/Devtools/{ALVY75L5.jsx → 3BAJISSX.jsx} +789 -258
- package/build/Devtools/{66PXWIOF.js → 4TNE5QTO.js} +961 -417
- package/build/Devtools/{H2EROWFU.jsx → OWSLDUSP.jsx} +789 -258
- package/build/Devtools/{HTDVDYMT.js → YSRICXZI.js} +961 -417
- package/build/dev.cjs +975 -427
- package/build/dev.js +1 -1
- package/build/dev.jsx +1 -1
- package/build/index.cjs +975 -427
- package/build/index.js +1 -1
- package/build/index.jsx +1 -1
- package/package.json +4 -4
- package/src/Context.ts +9 -0
- package/src/Devtools.tsx +424 -207
- package/src/Explorer.tsx +272 -39
- package/src/__tests__/utils.test.ts +725 -0
- package/src/icons/index.tsx +136 -18
- package/src/utils.tsx +154 -0
package/src/Explorer.tsx
CHANGED
|
@@ -4,8 +4,14 @@ import { clsx as cx } from 'clsx'
|
|
|
4
4
|
import { Index, Match, Show, Switch, createMemo, createSignal } from 'solid-js'
|
|
5
5
|
import { Key } from '@solid-primitives/keyed'
|
|
6
6
|
import { tokens } from './theme'
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
deleteNestedDataByPath,
|
|
9
|
+
displayValue,
|
|
10
|
+
updateNestedDataByPath,
|
|
11
|
+
} from './utils'
|
|
12
|
+
import { Check, CopiedCopier, Copier, ErrorCopier, List, Trash } from './icons'
|
|
13
|
+
import { useQueryDevtoolsContext, useTheme } from './Context'
|
|
14
|
+
import type { Query } from '@tanstack/query-core'
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Chunk elements in the array by size
|
|
@@ -31,12 +37,15 @@ export function chunkArray<T extends { label: string; value: unknown }>(
|
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
const Expander = (props: { expanded: boolean }) => {
|
|
34
|
-
const
|
|
40
|
+
const theme = useTheme()
|
|
41
|
+
const styles = createMemo(() => {
|
|
42
|
+
return theme() === 'dark' ? darkStyles : lightStyles
|
|
43
|
+
})
|
|
35
44
|
|
|
36
45
|
return (
|
|
37
46
|
<span
|
|
38
47
|
class={cx(
|
|
39
|
-
styles.expander,
|
|
48
|
+
styles().expander,
|
|
40
49
|
css`
|
|
41
50
|
transform: rotate(${props.expanded ? 90 : 0}deg);
|
|
42
51
|
`,
|
|
@@ -68,12 +77,16 @@ const Expander = (props: { expanded: boolean }) => {
|
|
|
68
77
|
|
|
69
78
|
type CopyState = 'NoCopy' | 'SuccessCopy' | 'ErrorCopy'
|
|
70
79
|
const CopyButton = (props: { value: unknown }) => {
|
|
71
|
-
const
|
|
80
|
+
const theme = useTheme()
|
|
81
|
+
const styles = createMemo(() => {
|
|
82
|
+
return theme() === 'dark' ? darkStyles : lightStyles
|
|
83
|
+
})
|
|
72
84
|
const [copyState, setCopyState] = createSignal<CopyState>('NoCopy')
|
|
73
85
|
|
|
74
86
|
return (
|
|
75
87
|
<button
|
|
76
|
-
class={styles.
|
|
88
|
+
class={styles().actionButton}
|
|
89
|
+
title="Copy object to clipboard"
|
|
77
90
|
aria-label={`${
|
|
78
91
|
copyState() === 'NoCopy'
|
|
79
92
|
? 'Copy object to clipboard'
|
|
@@ -108,7 +121,7 @@ const CopyButton = (props: { value: unknown }) => {
|
|
|
108
121
|
<Copier />
|
|
109
122
|
</Match>
|
|
110
123
|
<Match when={copyState() === 'SuccessCopy'}>
|
|
111
|
-
<CopiedCopier />
|
|
124
|
+
<CopiedCopier theme={theme()} />
|
|
112
125
|
</Match>
|
|
113
126
|
<Match when={copyState() === 'ErrorCopy'}>
|
|
114
127
|
<ErrorCopier />
|
|
@@ -118,11 +131,103 @@ const CopyButton = (props: { value: unknown }) => {
|
|
|
118
131
|
)
|
|
119
132
|
}
|
|
120
133
|
|
|
134
|
+
const ClearArrayButton = (props: {
|
|
135
|
+
dataPath: Array<string>
|
|
136
|
+
activeQuery: Query
|
|
137
|
+
}) => {
|
|
138
|
+
const theme = useTheme()
|
|
139
|
+
const styles = createMemo(() => {
|
|
140
|
+
return theme() === 'dark' ? darkStyles : lightStyles
|
|
141
|
+
})
|
|
142
|
+
const queryClient = useQueryDevtoolsContext().client
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<button
|
|
146
|
+
class={styles().actionButton}
|
|
147
|
+
title={'Remove all items'}
|
|
148
|
+
aria-label={'Remove all items'}
|
|
149
|
+
onClick={() => {
|
|
150
|
+
const oldData = props.activeQuery.state.data
|
|
151
|
+
const newData = updateNestedDataByPath(oldData, props.dataPath, [])
|
|
152
|
+
queryClient.setQueryData(props.activeQuery.queryKey, newData)
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<List />
|
|
156
|
+
</button>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const DeleteItemButton = (props: {
|
|
161
|
+
dataPath: Array<string>
|
|
162
|
+
activeQuery: Query
|
|
163
|
+
}) => {
|
|
164
|
+
const theme = useTheme()
|
|
165
|
+
const styles = createMemo(() => {
|
|
166
|
+
return theme() === 'dark' ? darkStyles : lightStyles
|
|
167
|
+
})
|
|
168
|
+
const queryClient = useQueryDevtoolsContext().client
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<button
|
|
172
|
+
class={cx(styles().actionButton)}
|
|
173
|
+
title={'Delete item'}
|
|
174
|
+
aria-label={'Delete item'}
|
|
175
|
+
onClick={() => {
|
|
176
|
+
const oldData = props.activeQuery.state.data
|
|
177
|
+
const newData = deleteNestedDataByPath(oldData, props.dataPath)
|
|
178
|
+
queryClient.setQueryData(props.activeQuery.queryKey, newData)
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<Trash />
|
|
182
|
+
</button>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const ToggleValueButton = (props: {
|
|
187
|
+
dataPath: Array<string>
|
|
188
|
+
activeQuery: Query
|
|
189
|
+
value: boolean
|
|
190
|
+
}) => {
|
|
191
|
+
const theme = useTheme()
|
|
192
|
+
const styles = createMemo(() => {
|
|
193
|
+
return theme() === 'dark' ? darkStyles : lightStyles
|
|
194
|
+
})
|
|
195
|
+
const queryClient = useQueryDevtoolsContext().client
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<button
|
|
199
|
+
class={cx(
|
|
200
|
+
styles().actionButton,
|
|
201
|
+
css`
|
|
202
|
+
width: ${tokens.size[3.5]};
|
|
203
|
+
height: ${tokens.size[3.5]};
|
|
204
|
+
`,
|
|
205
|
+
)}
|
|
206
|
+
title={'Toggle value'}
|
|
207
|
+
aria-label={'Toggle value'}
|
|
208
|
+
onClick={() => {
|
|
209
|
+
const oldData = props.activeQuery.state.data
|
|
210
|
+
const newData = updateNestedDataByPath(
|
|
211
|
+
oldData,
|
|
212
|
+
props.dataPath,
|
|
213
|
+
!props.value,
|
|
214
|
+
)
|
|
215
|
+
queryClient.setQueryData(props.activeQuery.queryKey, newData)
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
<Check theme={theme()} checked={props.value} />
|
|
219
|
+
</button>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
121
223
|
type ExplorerProps = {
|
|
122
|
-
|
|
224
|
+
editable?: boolean
|
|
123
225
|
label: string
|
|
124
226
|
value: unknown
|
|
125
227
|
defaultExpanded?: Array<string>
|
|
228
|
+
dataPath?: Array<string>
|
|
229
|
+
activeQuery?: Query
|
|
230
|
+
itemsDeletable?: boolean
|
|
126
231
|
}
|
|
127
232
|
|
|
128
233
|
function isIterable(x: any): x is Iterable<unknown> {
|
|
@@ -130,7 +235,11 @@ function isIterable(x: any): x is Iterable<unknown> {
|
|
|
130
235
|
}
|
|
131
236
|
|
|
132
237
|
export default function Explorer(props: ExplorerProps) {
|
|
133
|
-
const
|
|
238
|
+
const theme = useTheme()
|
|
239
|
+
const styles = createMemo(() => {
|
|
240
|
+
return theme() === 'dark' ? darkStyles : lightStyles
|
|
241
|
+
})
|
|
242
|
+
const queryClient = useQueryDevtoolsContext().client
|
|
134
243
|
|
|
135
244
|
const [expanded, setExpanded] = createSignal(
|
|
136
245
|
(props.defaultExpanded || []).includes(props.label),
|
|
@@ -187,27 +296,49 @@ export default function Explorer(props: ExplorerProps) {
|
|
|
187
296
|
|
|
188
297
|
const subEntryPages = createMemo(() => chunkArray(subEntries(), 100))
|
|
189
298
|
|
|
299
|
+
const currentDataPath = props.dataPath ?? []
|
|
300
|
+
|
|
190
301
|
return (
|
|
191
|
-
<div class={styles.entry}>
|
|
302
|
+
<div class={styles().entry}>
|
|
192
303
|
<Show when={subEntryPages().length}>
|
|
193
|
-
<div class={styles.expanderButtonContainer}>
|
|
304
|
+
<div class={styles().expanderButtonContainer}>
|
|
194
305
|
<button
|
|
195
|
-
class={styles.expanderButton}
|
|
306
|
+
class={styles().expanderButton}
|
|
196
307
|
onClick={() => toggleExpanded()}
|
|
197
308
|
>
|
|
198
309
|
<Expander expanded={expanded()} /> <span>{props.label}</span>{' '}
|
|
199
|
-
<span class={styles.info}>
|
|
310
|
+
<span class={styles().info}>
|
|
200
311
|
{String(type()).toLowerCase() === 'iterable' ? '(Iterable) ' : ''}
|
|
201
312
|
{subEntries().length} {subEntries().length > 1 ? `items` : `item`}
|
|
202
313
|
</span>
|
|
203
314
|
</button>
|
|
204
|
-
<Show when={props.
|
|
205
|
-
<
|
|
315
|
+
<Show when={props.editable}>
|
|
316
|
+
<div class={styles().actions}>
|
|
317
|
+
<CopyButton value={props.value} />
|
|
318
|
+
|
|
319
|
+
<Show
|
|
320
|
+
when={props.itemsDeletable && props.activeQuery !== undefined}
|
|
321
|
+
>
|
|
322
|
+
<DeleteItemButton
|
|
323
|
+
activeQuery={props.activeQuery!}
|
|
324
|
+
dataPath={currentDataPath}
|
|
325
|
+
/>
|
|
326
|
+
</Show>
|
|
327
|
+
|
|
328
|
+
<Show
|
|
329
|
+
when={type() === 'array' && props.activeQuery !== undefined}
|
|
330
|
+
>
|
|
331
|
+
<ClearArrayButton
|
|
332
|
+
activeQuery={props.activeQuery!}
|
|
333
|
+
dataPath={currentDataPath}
|
|
334
|
+
/>
|
|
335
|
+
</Show>
|
|
336
|
+
</div>
|
|
206
337
|
</Show>
|
|
207
338
|
</div>
|
|
208
339
|
<Show when={expanded()}>
|
|
209
340
|
<Show when={subEntryPages().length === 1}>
|
|
210
|
-
<div class={styles.subEntry}>
|
|
341
|
+
<div class={styles().subEntry}>
|
|
211
342
|
<Key each={subEntries()} by={(item) => item.label}>
|
|
212
343
|
{(entry) => {
|
|
213
344
|
return (
|
|
@@ -215,7 +346,14 @@ export default function Explorer(props: ExplorerProps) {
|
|
|
215
346
|
defaultExpanded={props.defaultExpanded}
|
|
216
347
|
label={entry().label}
|
|
217
348
|
value={entry().value}
|
|
218
|
-
|
|
349
|
+
editable={props.editable}
|
|
350
|
+
dataPath={[...currentDataPath, entry().label]}
|
|
351
|
+
activeQuery={props.activeQuery}
|
|
352
|
+
itemsDeletable={
|
|
353
|
+
type() === 'array' ||
|
|
354
|
+
type() === 'Iterable' ||
|
|
355
|
+
type() === 'object'
|
|
356
|
+
}
|
|
219
357
|
/>
|
|
220
358
|
)
|
|
221
359
|
}}
|
|
@@ -223,11 +361,11 @@ export default function Explorer(props: ExplorerProps) {
|
|
|
223
361
|
</div>
|
|
224
362
|
</Show>
|
|
225
363
|
<Show when={subEntryPages().length > 1}>
|
|
226
|
-
<div class={styles.subEntry}>
|
|
364
|
+
<div class={styles().subEntry}>
|
|
227
365
|
<Index each={subEntryPages()}>
|
|
228
366
|
{(entries, index) => (
|
|
229
367
|
<div>
|
|
230
|
-
<div class={styles.entry}>
|
|
368
|
+
<div class={styles().entry}>
|
|
231
369
|
<button
|
|
232
370
|
onClick={() =>
|
|
233
371
|
setExpandedPages((old) =>
|
|
@@ -236,21 +374,23 @@ export default function Explorer(props: ExplorerProps) {
|
|
|
236
374
|
: [...old, index],
|
|
237
375
|
)
|
|
238
376
|
}
|
|
239
|
-
class={styles.expanderButton}
|
|
377
|
+
class={styles().expanderButton}
|
|
240
378
|
>
|
|
241
379
|
<Expander expanded={expandedPages().includes(index)} />{' '}
|
|
242
380
|
[{index * 100}...
|
|
243
381
|
{index * 100 + 100 - 1}]
|
|
244
382
|
</button>
|
|
245
383
|
<Show when={expandedPages().includes(index)}>
|
|
246
|
-
<div class={styles.subEntry}>
|
|
384
|
+
<div class={styles().subEntry}>
|
|
247
385
|
<Key each={entries()} by={(entry) => entry.label}>
|
|
248
386
|
{(entry) => (
|
|
249
387
|
<Explorer
|
|
250
388
|
defaultExpanded={props.defaultExpanded}
|
|
251
389
|
label={entry().label}
|
|
252
390
|
value={entry().value}
|
|
253
|
-
|
|
391
|
+
editable={props.editable}
|
|
392
|
+
dataPath={[...currentDataPath, entry().label]}
|
|
393
|
+
activeQuery={props.activeQuery}
|
|
254
394
|
/>
|
|
255
395
|
)}
|
|
256
396
|
</Key>
|
|
@@ -265,22 +405,86 @@ export default function Explorer(props: ExplorerProps) {
|
|
|
265
405
|
</Show>
|
|
266
406
|
</Show>
|
|
267
407
|
<Show when={subEntryPages().length === 0}>
|
|
268
|
-
<div
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
408
|
+
<div class={styles().row}>
|
|
409
|
+
<span class={styles().label}>{props.label}:</span>
|
|
410
|
+
<Show
|
|
411
|
+
when={
|
|
412
|
+
props.editable &&
|
|
413
|
+
props.activeQuery !== undefined &&
|
|
414
|
+
(type() === 'string' ||
|
|
415
|
+
type() === 'number' ||
|
|
416
|
+
type() === 'boolean')
|
|
417
|
+
}
|
|
418
|
+
fallback={
|
|
419
|
+
<span class={styles().value}>{displayValue(props.value)}</span>
|
|
420
|
+
}
|
|
421
|
+
>
|
|
422
|
+
<Show
|
|
423
|
+
when={
|
|
424
|
+
props.editable &&
|
|
425
|
+
props.activeQuery !== undefined &&
|
|
426
|
+
(type() === 'string' || type() === 'number')
|
|
427
|
+
}
|
|
428
|
+
>
|
|
429
|
+
<input
|
|
430
|
+
type={type() === 'number' ? 'number' : 'text'}
|
|
431
|
+
class={cx(styles().value, styles().editableInput)}
|
|
432
|
+
value={props.value as string | number}
|
|
433
|
+
onChange={(changeEvent) => {
|
|
434
|
+
const oldData = props.activeQuery!.state.data
|
|
435
|
+
|
|
436
|
+
const newData = updateNestedDataByPath(
|
|
437
|
+
oldData,
|
|
438
|
+
currentDataPath,
|
|
439
|
+
type() === 'number'
|
|
440
|
+
? changeEvent.target.valueAsNumber
|
|
441
|
+
: changeEvent.target.value,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
queryClient.setQueryData(props.activeQuery!.queryKey, newData)
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
</Show>
|
|
448
|
+
|
|
449
|
+
<Show when={type() === 'boolean'}>
|
|
450
|
+
<span
|
|
451
|
+
class={cx(
|
|
452
|
+
styles().value,
|
|
453
|
+
styles().actions,
|
|
454
|
+
styles().editableInput,
|
|
455
|
+
)}
|
|
456
|
+
>
|
|
457
|
+
<ToggleValueButton
|
|
458
|
+
activeQuery={props.activeQuery!}
|
|
459
|
+
dataPath={currentDataPath}
|
|
460
|
+
value={props.value as boolean}
|
|
461
|
+
/>
|
|
462
|
+
{displayValue(props.value)}
|
|
463
|
+
</span>
|
|
464
|
+
</Show>
|
|
465
|
+
</Show>
|
|
466
|
+
|
|
467
|
+
<Show
|
|
468
|
+
when={
|
|
469
|
+
props.editable &&
|
|
470
|
+
props.itemsDeletable &&
|
|
471
|
+
props.activeQuery !== undefined
|
|
472
|
+
}
|
|
473
|
+
>
|
|
474
|
+
<DeleteItemButton
|
|
475
|
+
activeQuery={props.activeQuery!}
|
|
476
|
+
dataPath={currentDataPath}
|
|
477
|
+
/>
|
|
478
|
+
</Show>
|
|
275
479
|
</div>
|
|
276
480
|
</Show>
|
|
277
481
|
</div>
|
|
278
482
|
)
|
|
279
483
|
}
|
|
280
484
|
|
|
281
|
-
const
|
|
485
|
+
const stylesFactory = (theme: 'light' | 'dark') => {
|
|
282
486
|
const { colors, font, size, border } = tokens
|
|
283
|
-
|
|
487
|
+
const t = (light: string, dark: string) => (theme === 'light' ? light : dark)
|
|
284
488
|
return {
|
|
285
489
|
entry: css`
|
|
286
490
|
& * {
|
|
@@ -294,7 +498,7 @@ const getStyles = () => {
|
|
|
294
498
|
subEntry: css`
|
|
295
499
|
margin: 0 0 0 0.5em;
|
|
296
500
|
padding-left: 0.75em;
|
|
297
|
-
border-left: 2px solid ${colors.darkGray[400]};
|
|
501
|
+
border-left: 2px solid ${t(colors.gray[300], colors.darkGray[400])};
|
|
298
502
|
/* outline: 1px solid ${colors.teal[400]}; */
|
|
299
503
|
`,
|
|
300
504
|
expander: css`
|
|
@@ -315,6 +519,7 @@ const getStyles = () => {
|
|
|
315
519
|
align-items: center;
|
|
316
520
|
line-height: 1.125rem;
|
|
317
521
|
min-height: 1.125rem;
|
|
522
|
+
gap: ${size[2]};
|
|
318
523
|
`,
|
|
319
524
|
expanderButton: css`
|
|
320
525
|
cursor: pointer;
|
|
@@ -342,19 +547,45 @@ const getStyles = () => {
|
|
|
342
547
|
}
|
|
343
548
|
`,
|
|
344
549
|
info: css`
|
|
345
|
-
color: ${colors.gray[500]};
|
|
550
|
+
color: ${t(colors.gray[500], colors.gray[500])};
|
|
346
551
|
font-size: ${font.size.xs};
|
|
347
552
|
margin-left: ${size[1]};
|
|
348
553
|
/* outline: 1px solid ${colors.yellow[400]}; */
|
|
349
554
|
`,
|
|
350
555
|
label: css`
|
|
351
|
-
color: ${colors.gray[300]};
|
|
556
|
+
color: ${t(colors.gray[700], colors.gray[300])};
|
|
352
557
|
`,
|
|
353
558
|
value: css`
|
|
354
|
-
color: ${colors.purple[400]};
|
|
559
|
+
color: ${t(colors.purple[600], colors.purple[400])};
|
|
560
|
+
flex-grow: 1;
|
|
561
|
+
`,
|
|
562
|
+
actions: css`
|
|
563
|
+
display: inline-flex;
|
|
564
|
+
gap: ${size[2]};
|
|
565
|
+
align-items: center;
|
|
566
|
+
`,
|
|
567
|
+
row: css`
|
|
568
|
+
display: inline-flex;
|
|
569
|
+
gap: ${size[2]};
|
|
570
|
+
width: 100%;
|
|
571
|
+
margin-bottom: ${size[0.5]};
|
|
572
|
+
line-height: 1.125rem;
|
|
573
|
+
align-items: center;
|
|
574
|
+
`,
|
|
575
|
+
editableInput: css`
|
|
576
|
+
border: none;
|
|
577
|
+
padding: ${size[0.5]} ${size[1]} ${size[0.5]} ${size[1.5]};
|
|
578
|
+
flex-grow: 1;
|
|
579
|
+
border-radius: ${border.radius.xs};
|
|
580
|
+
background-color: ${t(colors.gray[200], colors.darkGray[500])};
|
|
581
|
+
|
|
582
|
+
&:hover {
|
|
583
|
+
background-color: ${t(colors.gray[300], colors.darkGray[600])};
|
|
584
|
+
}
|
|
355
585
|
`,
|
|
356
|
-
|
|
586
|
+
actionButton: css`
|
|
357
587
|
background-color: transparent;
|
|
588
|
+
color: ${t(colors.gray[500], colors.gray[500])};
|
|
358
589
|
border: none;
|
|
359
590
|
display: inline-flex;
|
|
360
591
|
padding: 0px;
|
|
@@ -364,11 +595,10 @@ const getStyles = () => {
|
|
|
364
595
|
width: ${size[3]};
|
|
365
596
|
height: ${size[3]};
|
|
366
597
|
position: relative;
|
|
367
|
-
left: ${size[2]};
|
|
368
598
|
z-index: 1;
|
|
369
599
|
|
|
370
|
-
&:hover svg
|
|
371
|
-
|
|
600
|
+
&:hover svg {
|
|
601
|
+
color: ${t(colors.gray[600], colors.gray[400])};
|
|
372
602
|
}
|
|
373
603
|
|
|
374
604
|
&:focus-visible {
|
|
@@ -379,3 +609,6 @@ const getStyles = () => {
|
|
|
379
609
|
`,
|
|
380
610
|
}
|
|
381
611
|
}
|
|
612
|
+
|
|
613
|
+
const lightStyles = stylesFactory('light')
|
|
614
|
+
const darkStyles = stylesFactory('dark')
|