@tanstack/query-devtools 5.52.3 → 5.54.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
@@ -2,30 +2,26 @@ import {
2
2
  For,
3
3
  Show,
4
4
  batch,
5
- createContext,
6
5
  createEffect,
7
6
  createMemo,
8
7
  createSignal,
9
8
  on,
10
9
  onCleanup,
11
10
  onMount,
12
- useContext,
13
11
  } from 'solid-js'
14
12
  import { rankItem } from '@tanstack/match-sorter-utils'
15
13
  import * as goober from 'goober'
16
14
  import { clsx as cx } from 'clsx'
17
15
  import { TransitionGroup } from 'solid-transition-group'
18
16
  import { Key } from '@solid-primitives/keyed'
19
- import { createLocalStorage } from '@solid-primitives/storage'
20
17
  import { createResizeObserver } from '@solid-primitives/resize-observer'
21
18
  import { DropdownMenu, RadioGroup } from '@kobalte/core'
22
- import { Portal, clearDelegatedEvents, delegateEvents } from 'solid-js/web'
19
+ import { Portal } from 'solid-js/web'
23
20
  import { tokens } from './theme'
24
21
  import {
25
22
  convertRemToPixels,
26
23
  displayValue,
27
24
  getMutationStatusColor,
28
- getPreferredColorScheme,
29
25
  getQueryStatusColor,
30
26
  getQueryStatusColorByLabel,
31
27
  getQueryStatusLabel,
@@ -55,18 +51,25 @@ import {
55
51
  XCircle,
56
52
  } from './icons'
57
53
  import Explorer from './Explorer'
54
+ import { usePiPWindow, useQueryDevtoolsContext, useTheme } from './contexts'
58
55
  import {
59
- QueryDevtoolsContext,
60
- ThemeContext,
61
- useQueryDevtoolsContext,
62
- useTheme,
63
- } from './Context'
56
+ BUTTON_POSITION,
57
+ DEFAULT_HEIGHT,
58
+ DEFAULT_MUTATION_SORT_FN_NAME,
59
+ DEFAULT_SORT_FN_NAME,
60
+ DEFAULT_SORT_ORDER,
61
+ DEFAULT_WIDTH,
62
+ INITIAL_IS_OPEN,
63
+ POSITION,
64
+ firstBreakpoint,
65
+ secondBreakpoint,
66
+ thirdBreakpoint,
67
+ } from './constants'
64
68
  import type {
65
- DevtoolsButtonPosition,
66
69
  DevtoolsErrorType,
67
70
  DevtoolsPosition,
68
71
  QueryDevtoolsProps,
69
- } from './Context'
72
+ } from './contexts'
70
73
  import type {
71
74
  Mutation,
72
75
  MutationCache,
@@ -83,26 +86,19 @@ interface DevtoolsPanelProps {
83
86
  setLocalStore: StorageSetter<string, unknown>
84
87
  }
85
88
 
89
+ interface ContentViewProps {
90
+ localStore: StorageObject<string>
91
+ setLocalStore: StorageSetter<string, unknown>
92
+ showPanelViewOnly?: boolean
93
+ onClose?: () => unknown
94
+ }
95
+
86
96
  interface QueryStatusProps {
87
97
  label: string
88
98
  color: 'green' | 'yellow' | 'gray' | 'blue' | 'purple' | 'red'
89
99
  count: number
90
100
  }
91
101
 
92
- const firstBreakpoint = 1024
93
- const secondBreakpoint = 796
94
- const thirdBreakpoint = 700
95
-
96
- const BUTTON_POSITION: DevtoolsButtonPosition = 'bottom-right'
97
- const POSITION: DevtoolsPosition = 'bottom'
98
- const THEME_PREFERENCE = 'system'
99
- const INITIAL_IS_OPEN = false
100
- const DEFAULT_HEIGHT = 500
101
- const DEFAULT_WIDTH = 500
102
- const DEFAULT_SORT_FN_NAME = Object.keys(sortFns)[0]
103
- const DEFAULT_SORT_ORDER = 1
104
- const DEFAULT_MUTATION_SORT_FN_NAME = Object.keys(mutationSortFns)[0]
105
-
106
102
  const [selectedQueryHash, setSelectedQueryHash] = createSignal<string | null>(
107
103
  null,
108
104
  )
@@ -116,211 +112,7 @@ export type DevtoolsComponentType = Component<QueryDevtoolsProps> & {
116
112
  shadowDOMTarget?: ShadowRoot
117
113
  }
118
114
 
119
- interface PiPProviderProps {
120
- children: JSX.Element
121
- localStore: StorageObject<string>
122
- setLocalStore: StorageSetter<string, unknown>
123
- }
124
-
125
- type PiPContextType = {
126
- pipWindow: Window | null
127
- requestPipWindow: (width: number, height: number) => Promise<void>
128
- closePipWindow: () => void
129
- }
130
-
131
- const PiPContext = createContext<Accessor<PiPContextType> | undefined>(
132
- undefined,
133
- )
134
-
135
- const PiPProvider = (props: PiPProviderProps) => {
136
- // Expose pipWindow that is currently active
137
- const [pipWindow, setPipWindow] = createSignal<Window | null>(null)
138
-
139
- // Close pipWindow programmatically
140
- const closePipWindow = () => {
141
- const w = pipWindow()
142
- if (w != null) {
143
- w.close()
144
- setPipWindow(null)
145
- }
146
- }
147
-
148
- // Open new pipWindow
149
- const requestPipWindow = async (width: number, height: number) => {
150
- // We don't want to allow multiple requests.
151
- if (pipWindow() != null) {
152
- return
153
- }
154
-
155
- const pip = window.open(
156
- '',
157
- 'TSQD-Devtools-Panel',
158
- `width=${width},height=${height},popup`,
159
- )
160
-
161
- if (!pip) {
162
- throw new Error(
163
- 'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.',
164
- )
165
- }
166
-
167
- // Remove existing styles
168
- pip.document.head.innerHTML = ''
169
- // Remove existing body
170
- pip.document.body.innerHTML = ''
171
- // Clear Delegated Events
172
- clearDelegatedEvents(pip.document)
173
-
174
- pip.document.title = 'TanStack Query Devtools'
175
- pip.document.body.style.margin = '0'
176
-
177
- // Detect when window is closed by user
178
- pip.addEventListener('pagehide', () => {
179
- props.setLocalStore('pip_open', 'false')
180
- setPipWindow(null)
181
- })
182
-
183
- // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all
184
- // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
185
- ;[
186
- ...(useQueryDevtoolsContext().shadowDOMTarget || document).styleSheets,
187
- ].forEach((styleSheet) => {
188
- try {
189
- const cssRules = [...styleSheet.cssRules]
190
- .map((rule) => rule.cssText)
191
- .join('')
192
- const style = document.createElement('style')
193
- const style_node = styleSheet.ownerNode
194
- let style_id = ''
195
-
196
- if (style_node && 'id' in style_node) {
197
- style_id = style_node.id
198
- }
199
-
200
- if (style_id) {
201
- style.setAttribute('id', style_id)
202
- }
203
- style.textContent = cssRules
204
- pip.document.head.appendChild(style)
205
- } catch (e) {
206
- const link = document.createElement('link')
207
- if (styleSheet.href == null) {
208
- return
209
- }
210
-
211
- link.rel = 'stylesheet'
212
- link.type = styleSheet.type
213
- link.media = styleSheet.media.toString()
214
- link.href = styleSheet.href
215
- pip.document.head.appendChild(link)
216
- }
217
- })
218
- delegateEvents(
219
- [
220
- 'focusin',
221
- 'focusout',
222
- 'pointermove',
223
- 'keydown',
224
- 'pointerdown',
225
- 'pointerup',
226
- 'click',
227
- 'mousedown',
228
- 'input',
229
- ],
230
- pip.document,
231
- )
232
- props.setLocalStore('pip_open', 'true')
233
- setPipWindow(pip)
234
- }
235
-
236
- createEffect(() => {
237
- const pip_open = (props.localStore.pip_open ?? 'false') as 'true' | 'false'
238
- if (pip_open === 'true') {
239
- requestPipWindow(
240
- Number(window.innerWidth),
241
- Number(props.localStore.height || DEFAULT_HEIGHT),
242
- )
243
- }
244
- })
245
-
246
- createEffect(() => {
247
- // Setup mutation observer for goober styles with id `_goober
248
- const gooberStyles = (
249
- useQueryDevtoolsContext().shadowDOMTarget || document
250
- ).querySelector('#_goober')
251
- const w = pipWindow()
252
- if (gooberStyles && w) {
253
- const observer = new MutationObserver(() => {
254
- const pip_style = (
255
- useQueryDevtoolsContext().shadowDOMTarget || w.document
256
- ).querySelector('#_goober')
257
- if (pip_style) {
258
- pip_style.textContent = gooberStyles.textContent
259
- }
260
- })
261
- observer.observe(gooberStyles, {
262
- childList: true, // observe direct children
263
- subtree: true, // and lower descendants too
264
- characterDataOldValue: true, // pass old data to callback
265
- })
266
- onCleanup(() => {
267
- observer.disconnect()
268
- })
269
- }
270
- })
271
-
272
- const value = createMemo(() => ({
273
- pipWindow: pipWindow(),
274
- requestPipWindow,
275
- closePipWindow,
276
- }))
277
-
278
- return (
279
- <PiPContext.Provider value={value}>{props.children}</PiPContext.Provider>
280
- )
281
- }
282
-
283
- const usePiPWindow = () => {
284
- const context = createMemo(() => {
285
- const ctx = useContext(PiPContext)
286
- if (!ctx) {
287
- throw new Error('usePiPWindow must be used within a PiPProvider')
288
- }
289
- return ctx()
290
- })
291
- return context
292
- }
293
-
294
- const DevtoolsComponent: DevtoolsComponentType = (props) => {
295
- const [localStore, setLocalStore] = createLocalStorage({
296
- prefix: 'TanstackQueryDevtools',
297
- })
298
-
299
- const colorScheme = getPreferredColorScheme()
300
-
301
- const theme = createMemo(() => {
302
- const preference = (localStore.theme_preference || THEME_PREFERENCE) as
303
- | 'system'
304
- | 'dark'
305
- | 'light'
306
- if (preference !== 'system') return preference
307
- return colorScheme()
308
- })
309
-
310
- return (
311
- <QueryDevtoolsContext.Provider value={props}>
312
- <PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
313
- <ThemeContext.Provider value={theme}>
314
- <Devtools localStore={localStore} setLocalStore={setLocalStore} />
315
- </ThemeContext.Provider>
316
- </PiPProvider>
317
- </QueryDevtoolsContext.Provider>
318
- )
319
- }
320
-
321
- export default DevtoolsComponent
322
-
323
- const Devtools: Component<DevtoolsPanelProps> = (props) => {
115
+ export const Devtools: Component<DevtoolsPanelProps> = (props) => {
324
116
  const theme = useTheme()
325
117
  const css = useQueryDevtoolsContext().shadowDOMTarget
326
118
  ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
@@ -393,10 +185,7 @@ const Devtools: Component<DevtoolsPanelProps> = (props) => {
393
185
  <Show when={pip().pipWindow && pip_open() == 'true'}>
394
186
  <Portal mount={pip().pipWindow?.document.body}>
395
187
  <PiPPanel>
396
- <ContentView
397
- localStore={props.localStore}
398
- setLocalStore={props.setLocalStore}
399
- />
188
+ <ContentView {...props} />
400
189
  </PiPPanel>
401
190
  </Portal>
402
191
  </Show>
@@ -444,7 +233,7 @@ const Devtools: Component<DevtoolsPanelProps> = (props) => {
444
233
  >
445
234
  <TransitionGroup name="tsqd-panel-transition">
446
235
  <Show when={isOpen() && !pip().pipWindow && pip_open() == 'false'}>
447
- <DevtoolsPanel
236
+ <DraggablePanel
448
237
  localStore={props.localStore}
449
238
  setLocalStore={props.setLocalStore}
450
239
  />
@@ -547,7 +336,66 @@ const PiPPanel: Component<{
547
336
  )
548
337
  }
549
338
 
550
- const DevtoolsPanel: Component<DevtoolsPanelProps> = (props) => {
339
+ export const ParentPanel: Component<{
340
+ children: JSX.Element
341
+ }> = (props) => {
342
+ const theme = useTheme()
343
+ const css = useQueryDevtoolsContext().shadowDOMTarget
344
+ ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
345
+ : goober.css
346
+ const styles = createMemo(() => {
347
+ return theme() === 'dark' ? darkStyles(css) : lightStyles(css)
348
+ })
349
+
350
+ let panelRef!: HTMLDivElement
351
+
352
+ onMount(() => {
353
+ createResizeObserver(panelRef, ({ width }, el) => {
354
+ if (el === panelRef) {
355
+ setPanelWidth(width)
356
+ }
357
+ })
358
+ })
359
+
360
+ const getPanelDynamicStyles = () => {
361
+ const { colors } = tokens
362
+ const t = (light: string, dark: string) =>
363
+ theme() === 'dark' ? dark : light
364
+ if (panelWidth() < secondBreakpoint) {
365
+ return css`
366
+ flex-direction: column;
367
+ background-color: ${t(colors.gray[300], colors.gray[600])};
368
+ `
369
+ }
370
+ return css`
371
+ flex-direction: row;
372
+ background-color: ${t(colors.gray[200], colors.darkGray[900])};
373
+ `
374
+ }
375
+
376
+ return (
377
+ <div
378
+ style={{
379
+ '--tsqd-font-size': '16px',
380
+ }}
381
+ ref={panelRef}
382
+ class={cx(
383
+ styles().parentPanel,
384
+ getPanelDynamicStyles(),
385
+ {
386
+ [css`
387
+ min-width: min-content;
388
+ `]: panelWidth() < thirdBreakpoint,
389
+ },
390
+ 'tsqd-main-panel',
391
+ )}
392
+ >
393
+ {props.children}
394
+ </div>
395
+ )
396
+ }
397
+
398
+ const DraggablePanel: Component<DevtoolsPanelProps> = (props) => {
551
399
  const theme = useTheme()
552
400
  const css = useQueryDevtoolsContext().shadowDOMTarget
553
401
  ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
@@ -740,18 +588,14 @@ const DevtoolsPanel: Component<DevtoolsPanelProps> = (props) => {
740
588
  >
741
589
  <ChevronDown />
742
590
  </button>
743
- <ContentView
744
- localStore={props.localStore}
745
- setLocalStore={props.setLocalStore}
746
- />
591
+ <ContentView {...props} />
747
592
  </aside>
748
593
  )
749
594
  }
750
595
 
751
- const ContentView: Component<DevtoolsPanelProps> = (props) => {
596
+ export const ContentView: Component<ContentViewProps> = (props) => {
752
597
  setupQueryCacheSubscription()
753
598
  setupMutationCacheSubscription()
754
-
755
599
  let containerRef!: HTMLDivElement
756
600
  const theme = useTheme()
757
601
  const css = useQueryDevtoolsContext().shadowDOMTarget
@@ -900,8 +744,12 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
900
744
  <button
901
745
  class={cx(styles().logo, 'tsqd-text-logo-container')}
902
746
  onClick={() => {
903
- if (!pip().pipWindow) {
747
+ if (!pip().pipWindow && !props.showPanelViewOnly) {
904
748
  props.setLocalStore('open', 'false')
749
+ return
750
+ }
751
+ if (props.onClose) {
752
+ props.onClose()
905
753
  }
906
754
  }}
907
755
  aria-label="Close Tanstack query devtools"
@@ -1118,7 +966,7 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
1118
966
  >
1119
967
  {offline() ? <Offline /> : <Wifi />}
1120
968
  </button>
1121
- <Show when={!pip().pipWindow}>
969
+ <Show when={!pip().pipWindow && !pip().disabled}>
1122
970
  <button
1123
971
  onClick={() => {
1124
972
  pip().requestPipWindow(
@@ -1167,90 +1015,92 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
1167
1015
  >
1168
1016
  Settings
1169
1017
  </div>
1170
- <DropdownMenu.Sub overlap gutter={8} shift={-4}>
1171
- <DropdownMenu.SubTrigger
1172
- class={cx(
1173
- styles().settingsSubTrigger,
1174
- 'tsqd-settings-menu-sub-trigger',
1175
- 'tsqd-settings-menu-sub-trigger-position',
1176
- )}
1177
- >
1178
- <span>Position</span>
1179
- <ChevronDown />
1180
- </DropdownMenu.SubTrigger>
1181
- <DropdownMenu.Portal
1182
- ref={(el) => setComputedVariables(el as HTMLDivElement)}
1183
- mount={
1184
- pip().pipWindow
1185
- ? pip().pipWindow!.document.body
1186
- : document.body
1187
- }
1188
- >
1189
- <DropdownMenu.SubContent
1018
+ <Show when={!props.showPanelViewOnly}>
1019
+ <DropdownMenu.Sub overlap gutter={8} shift={-4}>
1020
+ <DropdownMenu.SubTrigger
1190
1021
  class={cx(
1191
- styles().settingsMenu,
1192
- 'tsqd-settings-submenu',
1022
+ styles().settingsSubTrigger,
1023
+ 'tsqd-settings-menu-sub-trigger',
1024
+ 'tsqd-settings-menu-sub-trigger-position',
1193
1025
  )}
1194
1026
  >
1195
- <DropdownMenu.Item
1196
- onSelect={() => {
1197
- setDevtoolsPosition('top')
1198
- }}
1199
- as="button"
1200
- class={cx(
1201
- styles().settingsSubButton,
1202
- 'tsqd-settings-menu-position-btn',
1203
- 'tsqd-settings-menu-position-btn-top',
1204
- )}
1205
- >
1206
- <span>Top</span>
1207
- <ArrowUp />
1208
- </DropdownMenu.Item>
1209
- <DropdownMenu.Item
1210
- onSelect={() => {
1211
- setDevtoolsPosition('bottom')
1212
- }}
1213
- as="button"
1214
- class={cx(
1215
- styles().settingsSubButton,
1216
- 'tsqd-settings-menu-position-btn',
1217
- 'tsqd-settings-menu-position-btn-bottom',
1218
- )}
1219
- >
1220
- <span>Bottom</span>
1221
- <ArrowDown />
1222
- </DropdownMenu.Item>
1223
- <DropdownMenu.Item
1224
- onSelect={() => {
1225
- setDevtoolsPosition('left')
1226
- }}
1227
- as="button"
1228
- class={cx(
1229
- styles().settingsSubButton,
1230
- 'tsqd-settings-menu-position-btn',
1231
- 'tsqd-settings-menu-position-btn-left',
1232
- )}
1233
- >
1234
- <span>Left</span>
1235
- <ArrowLeft />
1236
- </DropdownMenu.Item>
1237
- <DropdownMenu.Item
1238
- onSelect={() => {
1239
- setDevtoolsPosition('right')
1240
- }}
1241
- as="button"
1027
+ <span>Position</span>
1028
+ <ChevronDown />
1029
+ </DropdownMenu.SubTrigger>
1030
+ <DropdownMenu.Portal
1031
+ ref={(el) => setComputedVariables(el as HTMLDivElement)}
1032
+ mount={
1033
+ pip().pipWindow
1034
+ ? pip().pipWindow!.document.body
1035
+ : document.body
1036
+ }
1037
+ >
1038
+ <DropdownMenu.SubContent
1242
1039
  class={cx(
1243
- styles().settingsSubButton,
1244
- 'tsqd-settings-menu-position-btn',
1245
- 'tsqd-settings-menu-position-btn-right',
1040
+ styles().settingsMenu,
1041
+ 'tsqd-settings-submenu',
1246
1042
  )}
1247
1043
  >
1248
- <span>Right</span>
1249
- <ArrowRight />
1250
- </DropdownMenu.Item>
1251
- </DropdownMenu.SubContent>
1252
- </DropdownMenu.Portal>
1253
- </DropdownMenu.Sub>
1044
+ <DropdownMenu.Item
1045
+ onSelect={() => {
1046
+ setDevtoolsPosition('top')
1047
+ }}
1048
+ as="button"
1049
+ class={cx(
1050
+ styles().settingsSubButton,
1051
+ 'tsqd-settings-menu-position-btn',
1052
+ 'tsqd-settings-menu-position-btn-top',
1053
+ )}
1054
+ >
1055
+ <span>Top</span>
1056
+ <ArrowUp />
1057
+ </DropdownMenu.Item>
1058
+ <DropdownMenu.Item
1059
+ onSelect={() => {
1060
+ setDevtoolsPosition('bottom')
1061
+ }}
1062
+ as="button"
1063
+ class={cx(
1064
+ styles().settingsSubButton,
1065
+ 'tsqd-settings-menu-position-btn',
1066
+ 'tsqd-settings-menu-position-btn-bottom',
1067
+ )}
1068
+ >
1069
+ <span>Bottom</span>
1070
+ <ArrowDown />
1071
+ </DropdownMenu.Item>
1072
+ <DropdownMenu.Item
1073
+ onSelect={() => {
1074
+ setDevtoolsPosition('left')
1075
+ }}
1076
+ as="button"
1077
+ class={cx(
1078
+ styles().settingsSubButton,
1079
+ 'tsqd-settings-menu-position-btn',
1080
+ 'tsqd-settings-menu-position-btn-left',
1081
+ )}
1082
+ >
1083
+ <span>Left</span>
1084
+ <ArrowLeft />
1085
+ </DropdownMenu.Item>
1086
+ <DropdownMenu.Item
1087
+ onSelect={() => {
1088
+ setDevtoolsPosition('right')
1089
+ }}
1090
+ as="button"
1091
+ class={cx(
1092
+ styles().settingsSubButton,
1093
+ 'tsqd-settings-menu-position-btn',
1094
+ 'tsqd-settings-menu-position-btn-right',
1095
+ )}
1096
+ >
1097
+ <span>Right</span>
1098
+ <ArrowRight />
1099
+ </DropdownMenu.Item>
1100
+ </DropdownMenu.SubContent>
1101
+ </DropdownMenu.Portal>
1102
+ </DropdownMenu.Sub>
1103
+ </Show>
1254
1104
  <DropdownMenu.Sub overlap gutter={8} shift={-4}>
1255
1105
  <DropdownMenu.SubTrigger
1256
1106
  class={cx(
@@ -1747,7 +1597,7 @@ const QueryStatus: Component<QueryStatusProps> = (props) => {
1747
1597
  return false
1748
1598
  }
1749
1599
  }
1750
- if (panelWidth() < thirdBreakpoint) {
1600
+ if (panelWidth() < secondBreakpoint) {
1751
1601
  return false
1752
1602
  }
1753
1603
 
@@ -2603,6 +2453,32 @@ const stylesFactory = (
2603
2453
  background: ${t(colors.gray[400], colors.darkGray[300])};
2604
2454
  }
2605
2455
  `,
2456
+ parentPanel: css`
2457
+ z-index: 9999;
2458
+ display: flex;
2459
+ height: 100%;
2460
+ gap: ${tokens.size[0.5]};
2461
+ & * {
2462
+ box-sizing: border-box;
2463
+ text-transform: none;
2464
+ }
2465
+
2466
+ & *::-webkit-scrollbar {
2467
+ width: 7px;
2468
+ }
2469
+
2470
+ & *::-webkit-scrollbar-track {
2471
+ background: transparent;
2472
+ }
2473
+
2474
+ & *::-webkit-scrollbar-thumb {
2475
+ background: ${t(colors.gray[300], colors.darkGray[200])};
2476
+ }
2477
+
2478
+ & *::-webkit-scrollbar-thumb:hover {
2479
+ background: ${t(colors.gray[400], colors.darkGray[300])};
2480
+ }
2481
+ `,
2606
2482
  'devtoolsBtn-position-bottom-right': css`
2607
2483
  bottom: 12px;
2608
2484
  right: 12px;
@@ -0,0 +1,36 @@
1
+ import { createLocalStorage } from '@solid-primitives/storage'
2
+ import { createMemo } from 'solid-js'
3
+ import { Devtools } from './Devtools'
4
+ import { getPreferredColorScheme } from './utils'
5
+ import { THEME_PREFERENCE } from './constants'
6
+ import { PiPProvider, QueryDevtoolsContext, ThemeContext } from './contexts'
7
+ import type { DevtoolsComponentType } from './Devtools'
8
+
9
+ const DevtoolsComponent: DevtoolsComponentType = (props) => {
10
+ const [localStore, setLocalStore] = createLocalStorage({
11
+ prefix: 'TanstackQueryDevtools',
12
+ })
13
+
14
+ const colorScheme = getPreferredColorScheme()
15
+
16
+ const theme = createMemo(() => {
17
+ const preference = (localStore.theme_preference || THEME_PREFERENCE) as
18
+ | 'system'
19
+ | 'dark'
20
+ | 'light'
21
+ if (preference !== 'system') return preference
22
+ return colorScheme()
23
+ })
24
+
25
+ return (
26
+ <QueryDevtoolsContext.Provider value={props}>
27
+ <PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
28
+ <ThemeContext.Provider value={theme}>
29
+ <Devtools localStore={localStore} setLocalStore={setLocalStore} />
30
+ </ThemeContext.Provider>
31
+ </PiPProvider>
32
+ </QueryDevtoolsContext.Provider>
33
+ )
34
+ }
35
+
36
+ export default DevtoolsComponent