@tanstack/query-devtools 5.0.0-beta.37 → 5.0.0-rc.10

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/index.js CHANGED
@@ -52,7 +52,7 @@ var TanstackQueryDevtools = class {
52
52
  if (this.Component) {
53
53
  Devtools = this.Component;
54
54
  } else {
55
- Devtools = lazy(() => import('./Devtools/66PXWIOF.js'));
55
+ Devtools = lazy(() => import('./Devtools/UZ6MTF6A.js'));
56
56
  this.Component = Devtools;
57
57
  }
58
58
  const _self$ = this;
package/build/index.jsx CHANGED
@@ -56,7 +56,7 @@ var TanstackQueryDevtools = class {
56
56
  if (this.Component) {
57
57
  Devtools = this.Component;
58
58
  } else {
59
- Devtools = lazy(() => import("./Devtools/ALVY75L5.jsx"));
59
+ Devtools = lazy(() => import("./Devtools/ECUC7UFN.jsx"));
60
60
  this.Component = Devtools;
61
61
  }
62
62
  return <Devtools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/query-devtools",
3
- "version": "5.0.0-beta.37",
3
+ "version": "5.0.0-rc.10",
4
4
  "description": "Developer tools to interact with and visualize the TanStack Query cache",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -17,8 +17,8 @@
17
17
  "browser": {},
18
18
  "exports": {
19
19
  "solid": {
20
- "development": "./build/dev.jsx",
21
- "import": "./build/index.jsx"
20
+ "development": "./build/index.js",
21
+ "import": "./build/index.js"
22
22
  },
23
23
  "development": {
24
24
  "import": {
@@ -50,7 +50,7 @@
50
50
  "superjson": "^1.12.4",
51
51
  "tsup-preset-solid": "^2.0.1",
52
52
  "vite-plugin-solid": "^2.7.0",
53
- "@tanstack/query-core": "5.0.0-beta.35"
53
+ "@tanstack/query-core": "5.0.0-rc.6"
54
54
  },
55
55
  "scripts": {
56
56
  "clean": "rimraf ./build && rimraf ./coverage",
package/src/Devtools.tsx CHANGED
@@ -1273,7 +1273,8 @@ const QueryDetails = () => {
1273
1273
  label="Data"
1274
1274
  defaultExpanded={['Data']}
1275
1275
  value={activeQueryStateData()}
1276
- copyable={true}
1276
+ editable={true}
1277
+ activeQuery={activeQuery()}
1277
1278
  />
1278
1279
  </div>
1279
1280
  <div class={cx(styles.detailsHeader, 'tsqd-query-details-header')}>
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 { displayValue } from './utils'
8
- import { CopiedCopier, Copier, ErrorCopier } from './icons'
7
+ import {
8
+ deleteNestedDataByPath,
9
+ displayValue,
10
+ updateNestedDataByPath,
11
+ } from './utils'
12
+ import { Check, CopiedCopier, Copier, ErrorCopier, List, Trash } from './icons'
13
+ import { useQueryDevtoolsContext } from './Context'
14
+ import type { Query } from '@tanstack/query-core'
9
15
 
10
16
  /**
11
17
  * Chunk elements in the array by size
@@ -73,7 +79,8 @@ const CopyButton = (props: { value: unknown }) => {
73
79
 
74
80
  return (
75
81
  <button
76
- class={styles.copyButton}
82
+ class={styles.actionButton}
83
+ title="Copy object to clipboard"
77
84
  aria-label={`${
78
85
  copyState() === 'NoCopy'
79
86
  ? 'Copy object to clipboard'
@@ -118,11 +125,88 @@ const CopyButton = (props: { value: unknown }) => {
118
125
  )
119
126
  }
120
127
 
128
+ const ClearArrayButton = (props: {
129
+ dataPath: Array<string>
130
+ activeQuery: Query
131
+ }) => {
132
+ const styles = getStyles()
133
+ const queryClient = useQueryDevtoolsContext().client
134
+
135
+ return (
136
+ <button
137
+ class={styles.actionButton}
138
+ title={'Remove all items'}
139
+ aria-label={'Remove all items'}
140
+ onClick={() => {
141
+ const oldData = props.activeQuery.state.data
142
+ const newData = updateNestedDataByPath(oldData, props.dataPath, [])
143
+ queryClient.setQueryData(props.activeQuery.queryKey, newData)
144
+ }}
145
+ >
146
+ <List />
147
+ </button>
148
+ )
149
+ }
150
+
151
+ const DeleteItemButton = (props: {
152
+ dataPath: Array<string>
153
+ activeQuery: Query
154
+ }) => {
155
+ const styles = getStyles()
156
+ const queryClient = useQueryDevtoolsContext().client
157
+
158
+ return (
159
+ <button
160
+ class={cx(styles.actionButton)}
161
+ title={'Delete item'}
162
+ aria-label={'Delete item'}
163
+ onClick={() => {
164
+ const oldData = props.activeQuery.state.data
165
+ const newData = deleteNestedDataByPath(oldData, props.dataPath)
166
+ queryClient.setQueryData(props.activeQuery.queryKey, newData)
167
+ }}
168
+ >
169
+ <Trash />
170
+ </button>
171
+ )
172
+ }
173
+
174
+ const ToggleValueButton = (props: {
175
+ dataPath: Array<string>
176
+ activeQuery: Query
177
+ value: boolean
178
+ }) => {
179
+ const styles = getStyles()
180
+ const queryClient = useQueryDevtoolsContext().client
181
+
182
+ return (
183
+ <button
184
+ class={cx(styles.actionButton)}
185
+ title={'Toggle value'}
186
+ aria-label={'Toggle value'}
187
+ onClick={() => {
188
+ const oldData = props.activeQuery.state.data
189
+ const newData = updateNestedDataByPath(
190
+ oldData,
191
+ props.dataPath,
192
+ !props.value,
193
+ )
194
+ queryClient.setQueryData(props.activeQuery.queryKey, newData)
195
+ }}
196
+ >
197
+ <Check checked={props.value} />
198
+ </button>
199
+ )
200
+ }
201
+
121
202
  type ExplorerProps = {
122
- copyable?: boolean
203
+ editable?: boolean
123
204
  label: string
124
205
  value: unknown
125
206
  defaultExpanded?: Array<string>
207
+ dataPath?: Array<string>
208
+ activeQuery?: Query
209
+ itemsDeletable?: boolean
126
210
  }
127
211
 
128
212
  function isIterable(x: any): x is Iterable<unknown> {
@@ -131,6 +215,7 @@ function isIterable(x: any): x is Iterable<unknown> {
131
215
 
132
216
  export default function Explorer(props: ExplorerProps) {
133
217
  const styles = getStyles()
218
+ const queryClient = useQueryDevtoolsContext().client
134
219
 
135
220
  const [expanded, setExpanded] = createSignal(
136
221
  (props.defaultExpanded || []).includes(props.label),
@@ -187,6 +272,8 @@ export default function Explorer(props: ExplorerProps) {
187
272
 
188
273
  const subEntryPages = createMemo(() => chunkArray(subEntries(), 100))
189
274
 
275
+ const currentDataPath = props.dataPath ?? []
276
+
190
277
  return (
191
278
  <div class={styles.entry}>
192
279
  <Show when={subEntryPages().length}>
@@ -201,8 +288,28 @@ export default function Explorer(props: ExplorerProps) {
201
288
  {subEntries().length} {subEntries().length > 1 ? `items` : `item`}
202
289
  </span>
203
290
  </button>
204
- <Show when={props.copyable}>
205
- <CopyButton value={props.value} />
291
+ <Show when={props.editable}>
292
+ <div class={styles.actions}>
293
+ <CopyButton value={props.value} />
294
+
295
+ <Show
296
+ when={props.itemsDeletable && props.activeQuery !== undefined}
297
+ >
298
+ <DeleteItemButton
299
+ activeQuery={props.activeQuery!}
300
+ dataPath={currentDataPath}
301
+ />
302
+ </Show>
303
+
304
+ <Show
305
+ when={type() === 'array' && props.activeQuery !== undefined}
306
+ >
307
+ <ClearArrayButton
308
+ activeQuery={props.activeQuery!}
309
+ dataPath={currentDataPath}
310
+ />
311
+ </Show>
312
+ </div>
206
313
  </Show>
207
314
  </div>
208
315
  <Show when={expanded()}>
@@ -215,7 +322,14 @@ export default function Explorer(props: ExplorerProps) {
215
322
  defaultExpanded={props.defaultExpanded}
216
323
  label={entry().label}
217
324
  value={entry().value}
218
- copyable={props.copyable}
325
+ editable={props.editable}
326
+ dataPath={[...currentDataPath, entry().label]}
327
+ activeQuery={props.activeQuery}
328
+ itemsDeletable={
329
+ type() === 'array' ||
330
+ type() === 'Iterable' ||
331
+ type() === 'object'
332
+ }
219
333
  />
220
334
  )
221
335
  }}
@@ -250,7 +364,9 @@ export default function Explorer(props: ExplorerProps) {
250
364
  defaultExpanded={props.defaultExpanded}
251
365
  label={entry().label}
252
366
  value={entry().value}
253
- copyable={props.copyable}
367
+ editable={props.editable}
368
+ dataPath={[...currentDataPath, entry().label]}
369
+ activeQuery={props.activeQuery}
254
370
  />
255
371
  )}
256
372
  </Key>
@@ -265,13 +381,73 @@ export default function Explorer(props: ExplorerProps) {
265
381
  </Show>
266
382
  </Show>
267
383
  <Show when={subEntryPages().length === 0}>
268
- <div
269
- style={{
270
- 'line-height': '1.125rem',
271
- }}
272
- >
273
- <span class={styles.label}>{props.label}:</span>{' '}
274
- <span class={styles.value}>{displayValue(props.value)}</span>
384
+ <div class={styles.row}>
385
+ <span class={styles.label}>{props.label}:</span>
386
+ <Show
387
+ when={
388
+ props.editable &&
389
+ props.activeQuery !== undefined &&
390
+ (type() === 'string' ||
391
+ type() === 'number' ||
392
+ type() === 'boolean')
393
+ }
394
+ fallback={
395
+ <span class={styles.value}>{displayValue(props.value)}</span>
396
+ }
397
+ >
398
+ <Show
399
+ when={
400
+ props.editable &&
401
+ props.activeQuery !== undefined &&
402
+ (type() === 'string' || type() === 'number')
403
+ }
404
+ >
405
+ <input
406
+ type={type() === 'number' ? 'number' : 'text'}
407
+ class={cx(styles.value, styles.editableInput)}
408
+ value={props.value as string | number}
409
+ onChange={(changeEvent) => {
410
+ const oldData = props.activeQuery!.state.data
411
+
412
+ const newData = updateNestedDataByPath(
413
+ oldData,
414
+ currentDataPath,
415
+ type() === 'number'
416
+ ? changeEvent.target.valueAsNumber
417
+ : changeEvent.target.value,
418
+ )
419
+
420
+ queryClient.setQueryData(props.activeQuery!.queryKey, newData)
421
+ }}
422
+ />
423
+ </Show>
424
+
425
+ <Show when={type() === 'boolean'}>
426
+ <span
427
+ class={cx(styles.value, styles.actions, styles.editableInput)}
428
+ >
429
+ <ToggleValueButton
430
+ activeQuery={props.activeQuery!}
431
+ dataPath={currentDataPath}
432
+ value={props.value as boolean}
433
+ />
434
+ {displayValue(props.value)}
435
+ </span>
436
+ </Show>
437
+ </Show>
438
+
439
+ <Show
440
+ when={
441
+ props.editable &&
442
+ props.itemsDeletable &&
443
+ props.activeQuery !== undefined
444
+ }
445
+ >
446
+ <DeleteItemButton
447
+ activeQuery={props.activeQuery!}
448
+ dataPath={currentDataPath}
449
+ />
450
+ </Show>
275
451
  </div>
276
452
  </Show>
277
453
  </div>
@@ -315,6 +491,7 @@ const getStyles = () => {
315
491
  align-items: center;
316
492
  line-height: 1.125rem;
317
493
  min-height: 1.125rem;
494
+ gap: ${size[2]};
318
495
  `,
319
496
  expanderButton: css`
320
497
  cursor: pointer;
@@ -352,8 +529,32 @@ const getStyles = () => {
352
529
  `,
353
530
  value: css`
354
531
  color: ${colors.purple[400]};
532
+ flex-grow: 1;
533
+ `,
534
+ actions: css`
535
+ display: inline-flex;
536
+ gap: ${size[2]};
537
+ align-items: center;
538
+ `,
539
+ row: css`
540
+ display: inline-flex;
541
+ gap: ${size[2]};
542
+ width: 100%;
543
+ margin-bottom: ${size[0.5]};
544
+ line-height: 1.125rem;
545
+ align-items: center;
355
546
  `,
356
- copyButton: css`
547
+ editableInput: css`
548
+ border: none;
549
+ padding: ${size[0.5]} ${size[1]};
550
+ flex-grow: 1;
551
+ background-color: ${colors.gray[900]};
552
+
553
+ &:hover {
554
+ background-color: ${colors.gray[800]};
555
+ }
556
+ `,
557
+ actionButton: css`
357
558
  background-color: transparent;
358
559
  border: none;
359
560
  display: inline-flex;
@@ -364,11 +565,18 @@ const getStyles = () => {
364
565
  width: ${size[3]};
365
566
  height: ${size[3]};
366
567
  position: relative;
367
- left: ${size[2]};
368
568
  z-index: 1;
369
569
 
370
- &:hover svg .copier {
371
- stroke: ${colors.gray[500]} !important;
570
+ &:hover svg {
571
+ .copier,
572
+ .check,
573
+ .list {
574
+ stroke: ${colors.gray[500]} !important;
575
+ }
576
+
577
+ .list-item {
578
+ stroke: ${colors.gray[700]};
579
+ }
372
580
  }
373
581
 
374
582
  &:focus-visible {