@tanstack/query-devtools 5.91.1 → 5.92.0

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/src/Devtools.tsx CHANGED
@@ -417,6 +417,13 @@ const DraggablePanel: Component<DevtoolsPanelProps> = (props) => {
417
417
  return theme() === 'dark' ? darkStyles(css) : lightStyles(css)
418
418
  })
419
419
 
420
+ let closeBtnRef!: HTMLButtonElement
421
+
422
+ // Focus the close button when the panel opens for screen reader accessibility
423
+ onMount(() => {
424
+ closeBtnRef.focus()
425
+ })
426
+
420
427
  const [isResizing, setIsResizing] = createSignal(false)
421
428
 
422
429
  const position = createMemo(
@@ -480,7 +487,7 @@ const DraggablePanel: Component<DevtoolsPanelProps> = (props) => {
480
487
  setIsResizing(false)
481
488
  }
482
489
  document.removeEventListener('mousemove', runDrag, false)
483
- document.removeEventListener('mouseUp', unsubscribe, false)
490
+ document.removeEventListener('mouseup', unsubscribe, false)
484
491
  }
485
492
 
486
493
  document.addEventListener('mousemove', runDrag, false)
@@ -583,14 +590,73 @@ const DraggablePanel: Component<DevtoolsPanelProps> = (props) => {
583
590
  aria-label="Tanstack query devtools"
584
591
  >
585
592
  <div
593
+ role="separator"
594
+ aria-orientation={
595
+ position() === 'top' || position() === 'bottom'
596
+ ? 'horizontal'
597
+ : 'vertical'
598
+ }
599
+ aria-label="Resize devtools panel"
600
+ aria-valuemin={
601
+ position() === 'top' || position() === 'bottom'
602
+ ? convertRemToPixels(3.5)
603
+ : convertRemToPixels(12)
604
+ }
605
+ aria-valuenow={
606
+ position() === 'top' || position() === 'bottom'
607
+ ? Number(props.localStore.height || DEFAULT_HEIGHT)
608
+ : Number(props.localStore.width || DEFAULT_WIDTH)
609
+ }
610
+ tabindex="0"
586
611
  class={cx(
587
612
  styles().dragHandle,
588
613
  styles()[`dragHandle-position-${position()}`],
589
614
  'tsqd-drag-handle',
590
615
  )}
591
616
  onMouseDown={handleDragStart}
617
+ onKeyDown={(e) => {
618
+ const step = 10
619
+ const minHeight = convertRemToPixels(3.5)
620
+ const minWidth = convertRemToPixels(12)
621
+ if (position() === 'top' || position() === 'bottom') {
622
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
623
+ e.preventDefault()
624
+ const currentHeight = Number(
625
+ props.localStore.height || DEFAULT_HEIGHT,
626
+ )
627
+ const delta =
628
+ position() === 'bottom'
629
+ ? e.key === 'ArrowUp'
630
+ ? step
631
+ : -step
632
+ : e.key === 'ArrowDown'
633
+ ? step
634
+ : -step
635
+ const newHeight = Math.max(minHeight, currentHeight + delta)
636
+ props.setLocalStore('height', String(newHeight))
637
+ }
638
+ } else {
639
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
640
+ e.preventDefault()
641
+ const currentWidth = Number(
642
+ props.localStore.width || DEFAULT_WIDTH,
643
+ )
644
+ const delta =
645
+ position() === 'right'
646
+ ? e.key === 'ArrowLeft'
647
+ ? step
648
+ : -step
649
+ : e.key === 'ArrowRight'
650
+ ? step
651
+ : -step
652
+ const newWidth = Math.max(minWidth, currentWidth + delta)
653
+ props.setLocalStore('width', String(newWidth))
654
+ }
655
+ }
656
+ }}
592
657
  ></div>
593
658
  <button
659
+ ref={closeBtnRef}
594
660
  aria-label="Close tanstack query devtools"
595
661
  class={cx(
596
662
  styles().closeBtn,
@@ -796,6 +862,7 @@ export const ContentView: Component<ContentViewProps> = (props) => {
796
862
  <RadioGroup.Root
797
863
  class={cx(styles().viewToggle)}
798
864
  value={selectedView()}
865
+ aria-label="Toggle between queries and mutations view"
799
866
  onChange={(value) => {
800
867
  setSelectedView(value as 'queries' | 'mutations')
801
868
  setSelectedQueryHash(null)
@@ -869,6 +936,7 @@ export const ContentView: Component<ContentViewProps> = (props) => {
869
936
  <select
870
937
  value={sort()}
871
938
  name="tsqd-queries-filter-sort"
939
+ aria-label="Sort queries by"
872
940
  onChange={(e) => {
873
941
  props.setLocalStore('sort', e.currentTarget.value)
874
942
  }}
@@ -882,6 +950,7 @@ export const ContentView: Component<ContentViewProps> = (props) => {
882
950
  <select
883
951
  value={mutationSort()}
884
952
  name="tsqd-mutations-filter-sort"
953
+ aria-label="Sort mutations by"
885
954
  onChange={(e) => {
886
955
  props.setLocalStore('mutationSort', e.currentTarget.value)
887
956
  }}
@@ -1212,10 +1281,10 @@ export const ContentView: Component<ContentViewProps> = (props) => {
1212
1281
  styles().settingsMenu,
1213
1282
  'tsqd-settings-submenu',
1214
1283
  )}
1215
- aria-label="Hide disabled queries setting"
1216
1284
  >
1217
1285
  <DropdownMenu.RadioGroup
1218
1286
  value={props.localStore.hideDisabledQueries}
1287
+ aria-label="Hide disabled queries setting"
1219
1288
  onChange={(value) =>
1220
1289
  props.setLocalStore('hideDisabledQueries', value)
1221
1290
  }
@@ -1409,7 +1478,7 @@ const QueryRow: Component<{ query: Query }> = (props) => {
1409
1478
  styles().selectedQueryRow,
1410
1479
  'tsqd-query-row',
1411
1480
  )}
1412
- aria-label={`Query key ${props.query.queryHash}`}
1481
+ aria-label={`Query key ${props.query.queryHash}${isDisabled() ? ', disabled' : ''}${isStatic() ? ', static' : ''}`}
1413
1482
  >
1414
1483
  <div
1415
1484
  class={cx(getObserverCountColorStyles(), 'tsqd-query-observer-count')}
@@ -1418,10 +1487,14 @@ const QueryRow: Component<{ query: Query }> = (props) => {
1418
1487
  </div>
1419
1488
  <code class="tsqd-query-hash">{props.query.queryHash}</code>
1420
1489
  <Show when={isDisabled()}>
1421
- <div class="tsqd-query-disabled-indicator">disabled</div>
1490
+ <div class="tsqd-query-disabled-indicator" aria-hidden="true">
1491
+ disabled
1492
+ </div>
1422
1493
  </Show>
1423
1494
  <Show when={isStatic()}>
1424
- <div class="tsqd-query-static-indicator">static</div>
1495
+ <div class="tsqd-query-static-indicator" aria-hidden="true">
1496
+ static
1497
+ </div>
1425
1498
  </Show>
1426
1499
  </button>
1427
1500
  </Show>
@@ -1710,6 +1783,7 @@ const QueryStatus: Component<QueryStatusProps> = (props) => {
1710
1783
  }}
1711
1784
  disabled={showLabel()}
1712
1785
  ref={tagRef}
1786
+ aria-label={`${props.label}: ${props.count}`}
1713
1787
  class={cx(
1714
1788
  styles().queryStatusTag,
1715
1789
  !showLabel() &&
@@ -1741,6 +1815,7 @@ const QueryStatus: Component<QueryStatusProps> = (props) => {
1741
1815
  </div>
1742
1816
  </Show>
1743
1817
  <span
1818
+ aria-hidden="true"
1744
1819
  class={cx(
1745
1820
  css`
1746
1821
  width: ${tokens.size[1.5]};
@@ -1942,7 +2017,11 @@ const QueryDetails = () => {
1942
2017
  <div
1943
2018
  class={cx(styles().detailsContainer, 'tsqd-query-details-container')}
1944
2019
  >
1945
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2020
+ <div
2021
+ role="heading"
2022
+ aria-level="2"
2023
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2024
+ >
1946
2025
  Query Details
1947
2026
  </div>
1948
2027
  <div
@@ -1950,9 +2029,6 @@ const QueryDetails = () => {
1950
2029
  styles().detailsBody,
1951
2030
  'tsqd-query-details-summary-container',
1952
2031
  )}
1953
- role="status"
1954
- aria-live="polite"
1955
- aria-atomic="true"
1956
2032
  >
1957
2033
  <div class="tsqd-query-details-summary">
1958
2034
  <pre>
@@ -1960,6 +2036,8 @@ const QueryDetails = () => {
1960
2036
  </pre>
1961
2037
  <span
1962
2038
  class={cx(styles().queryDetailsStatus, getQueryStatusColors())}
2039
+ role="status"
2040
+ aria-live="polite"
1963
2041
  >
1964
2042
  {statusLabel()}
1965
2043
  </span>
@@ -1975,7 +2053,11 @@ const QueryDetails = () => {
1975
2053
  </span>
1976
2054
  </div>
1977
2055
  </div>
1978
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2056
+ <div
2057
+ role="heading"
2058
+ aria-level="2"
2059
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2060
+ >
1979
2061
  Actions
1980
2062
  </div>
1981
2063
  <div
@@ -1996,6 +2078,7 @@ const QueryDetails = () => {
1996
2078
  disabled={statusLabel() === 'fetching'}
1997
2079
  >
1998
2080
  <span
2081
+ aria-hidden="true"
1999
2082
  class={css`
2000
2083
  background-color: ${t(colors.blue[600], colors.blue[400])};
2001
2084
  `}
@@ -2020,6 +2103,7 @@ const QueryDetails = () => {
2020
2103
  disabled={queryStatus() === 'pending'}
2021
2104
  >
2022
2105
  <span
2106
+ aria-hidden="true"
2023
2107
  class={css`
2024
2108
  background-color: ${t(colors.yellow[600], colors.yellow[400])};
2025
2109
  `}
@@ -2044,6 +2128,7 @@ const QueryDetails = () => {
2044
2128
  disabled={queryStatus() === 'pending'}
2045
2129
  >
2046
2130
  <span
2131
+ aria-hidden="true"
2047
2132
  class={css`
2048
2133
  background-color: ${t(colors.gray[600], colors.gray[400])};
2049
2134
  `}
@@ -2069,6 +2154,7 @@ const QueryDetails = () => {
2069
2154
  disabled={statusLabel() === 'fetching'}
2070
2155
  >
2071
2156
  <span
2157
+ aria-hidden="true"
2072
2158
  class={css`
2073
2159
  background-color: ${t(colors.pink[500], colors.pink[400])};
2074
2160
  `}
@@ -2118,6 +2204,7 @@ const QueryDetails = () => {
2118
2204
  }}
2119
2205
  >
2120
2206
  <span
2207
+ aria-hidden="true"
2121
2208
  class={css`
2122
2209
  background-color: ${t(colors.cyan[500], colors.cyan[400])};
2123
2210
  `}
@@ -2147,6 +2234,7 @@ const QueryDetails = () => {
2147
2234
  disabled={queryStatus() === 'pending'}
2148
2235
  >
2149
2236
  <span
2237
+ aria-hidden="true"
2150
2238
  class={css`
2151
2239
  background-color: ${t(colors.red[500], colors.red[400])};
2152
2240
  `}
@@ -2165,6 +2253,7 @@ const QueryDetails = () => {
2165
2253
  )}
2166
2254
  >
2167
2255
  <span
2256
+ aria-hidden="true"
2168
2257
  class={css`
2169
2258
  background-color: ${tokens.colors.red[400]};
2170
2259
  `}
@@ -2172,6 +2261,7 @@ const QueryDetails = () => {
2172
2261
  Trigger Error
2173
2262
  <select
2174
2263
  disabled={queryStatus() === 'pending'}
2264
+ aria-label="Select error type to trigger"
2175
2265
  onChange={(e) => {
2176
2266
  const errorType = errorTypes().find(
2177
2267
  (et) => et.name === e.currentTarget.value,
@@ -2191,7 +2281,11 @@ const QueryDetails = () => {
2191
2281
  </div>
2192
2282
  </Show>
2193
2283
  </div>
2194
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2284
+ <div
2285
+ role="heading"
2286
+ aria-level="2"
2287
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2288
+ >
2195
2289
  Data {dataMode() === 'view' ? 'Explorer' : 'Editor'}
2196
2290
  </div>
2197
2291
  <Show when={dataMode() === 'view'}>
@@ -2235,6 +2329,7 @@ const QueryDetails = () => {
2235
2329
  >
2236
2330
  <textarea
2237
2331
  name="data"
2332
+ aria-label="Edit query data as JSON"
2238
2333
  class={styles().devtoolsEditTextarea}
2239
2334
  onFocus={() => setDataEditError(false)}
2240
2335
  data-error={dataEditError()}
@@ -2271,7 +2366,11 @@ const QueryDetails = () => {
2271
2366
  </div>
2272
2367
  </form>
2273
2368
  </Show>
2274
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2369
+ <div
2370
+ role="heading"
2371
+ aria-level="2"
2372
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2373
+ >
2275
2374
  Query Explorer
2276
2375
  </div>
2277
2376
  <div
@@ -2356,7 +2455,11 @@ const MutationDetails = () => {
2356
2455
  <div
2357
2456
  class={cx(styles().detailsContainer, 'tsqd-query-details-container')}
2358
2457
  >
2359
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2458
+ <div
2459
+ role="heading"
2460
+ aria-level="2"
2461
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2462
+ >
2360
2463
  Mutation Details
2361
2464
  </div>
2362
2465
  <div
@@ -2364,9 +2467,6 @@ const MutationDetails = () => {
2364
2467
  styles().detailsBody,
2365
2468
  'tsqd-query-details-summary-container',
2366
2469
  )}
2367
- role="status"
2368
- aria-live="polite"
2369
- aria-atomic="true"
2370
2470
  >
2371
2471
  <div class="tsqd-query-details-summary">
2372
2472
  <pre>
@@ -2381,6 +2481,8 @@ const MutationDetails = () => {
2381
2481
  </pre>
2382
2482
  <span
2383
2483
  class={cx(styles().queryDetailsStatus, getQueryStatusColors())}
2484
+ role="status"
2485
+ aria-live="polite"
2384
2486
  >
2385
2487
  <Show when={color() === 'purple'}>pending</Show>
2386
2488
  <Show when={color() !== 'purple'}>{status()}</Show>
@@ -2395,7 +2497,11 @@ const MutationDetails = () => {
2395
2497
  </span>
2396
2498
  </div>
2397
2499
  </div>
2398
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2500
+ <div
2501
+ role="heading"
2502
+ aria-level="2"
2503
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2504
+ >
2399
2505
  Variables Details
2400
2506
  </div>
2401
2507
  <div
@@ -2410,7 +2516,11 @@ const MutationDetails = () => {
2410
2516
  value={activeMutation()!.state.variables}
2411
2517
  />
2412
2518
  </div>
2413
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2519
+ <div
2520
+ role="heading"
2521
+ aria-level="2"
2522
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2523
+ >
2414
2524
  Context Details
2415
2525
  </div>
2416
2526
  <div
@@ -2425,7 +2535,11 @@ const MutationDetails = () => {
2425
2535
  value={activeMutation()!.state.context}
2426
2536
  />
2427
2537
  </div>
2428
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2538
+ <div
2539
+ role="heading"
2540
+ aria-level="2"
2541
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2542
+ >
2429
2543
  Data Explorer
2430
2544
  </div>
2431
2545
  <div
@@ -2440,7 +2554,11 @@ const MutationDetails = () => {
2440
2554
  value={activeMutation()!.state.data}
2441
2555
  />
2442
2556
  </div>
2443
- <div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
2557
+ <div
2558
+ role="heading"
2559
+ aria-level="2"
2560
+ class={cx(styles().detailsHeader, 'tsqd-query-details-header')}
2561
+ >
2444
2562
  Mutations Explorer
2445
2563
  </div>
2446
2564
  <div
@@ -2897,6 +3015,15 @@ const stylesFactory = (
2897
3015
  &:hover {
2898
3016
  background-color: ${colors.purple[400]}${t('', alpha[90])};
2899
3017
  }
3018
+ &:focus {
3019
+ outline: none;
3020
+ background-color: ${colors.purple[400]}${t('', alpha[90])};
3021
+ }
3022
+ &:focus-visible {
3023
+ outline: 2px solid ${colors.blue[800]};
3024
+ outline-offset: -2px;
3025
+ background-color: ${colors.purple[400]}${t('', alpha[90])};
3026
+ }
2900
3027
  z-index: 4;
2901
3028
  `,
2902
3029
  'dragHandle-position-top': css`
@@ -26,6 +26,8 @@ type PiPContextType = {
26
26
  disabled: boolean
27
27
  }
28
28
 
29
+ class PipOpenError extends Error {}
30
+
29
31
  const PiPContext = createContext<Accessor<PiPContextType> | undefined>(
30
32
  undefined,
31
33
  )
@@ -57,7 +59,7 @@ export const PiPProvider = (props: PiPProviderProps) => {
57
59
  )
58
60
 
59
61
  if (!pip) {
60
- throw new Error(
62
+ throw new PipOpenError(
61
63
  'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.',
62
64
  )
63
65
  }
@@ -134,10 +136,20 @@ export const PiPProvider = (props: PiPProviderProps) => {
134
136
  createEffect(() => {
135
137
  const pip_open = (props.localStore.pip_open ?? 'false') as 'true' | 'false'
136
138
  if (pip_open === 'true' && !props.disabled) {
137
- requestPipWindow(
138
- Number(window.innerWidth),
139
- Number(props.localStore.height || PIP_DEFAULT_HEIGHT),
140
- )
139
+ try {
140
+ requestPipWindow(
141
+ Number(window.innerWidth),
142
+ Number(props.localStore.height || PIP_DEFAULT_HEIGHT),
143
+ )
144
+ } catch (error) {
145
+ if (error instanceof PipOpenError) {
146
+ console.error(error.message)
147
+ props.setLocalStore('pip_open', 'false')
148
+ props.setLocalStore('open', 'false')
149
+ return
150
+ }
151
+ throw error
152
+ }
141
153
  }
142
154
  })
143
155