@tanstack/query-devtools 5.52.3 → 5.55.1

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
  />
@@ -463,6 +252,7 @@ const Devtools: Component<DevtoolsPanelProps> = (props) => {
463
252
  <TanstackLogo />
464
253
  </div>
465
254
  <button
255
+ type="button"
466
256
  aria-label="Open Tanstack query devtools"
467
257
  onClick={() => props.setLocalStore('open', 'true')}
468
258
  class="tsqd-open-btn"
@@ -547,7 +337,66 @@ const PiPPanel: Component<{
547
337
  )
548
338
  }
549
339
 
550
- const DevtoolsPanel: Component<DevtoolsPanelProps> = (props) => {
340
+ export const ParentPanel: Component<{
341
+ children: JSX.Element
342
+ }> = (props) => {
343
+ const theme = useTheme()
344
+ const css = useQueryDevtoolsContext().shadowDOMTarget
345
+ ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
346
+ : goober.css
347
+ const styles = createMemo(() => {
348
+ return theme() === 'dark' ? darkStyles(css) : lightStyles(css)
349
+ })
350
+
351
+ let panelRef!: HTMLDivElement
352
+
353
+ onMount(() => {
354
+ createResizeObserver(panelRef, ({ width }, el) => {
355
+ if (el === panelRef) {
356
+ setPanelWidth(width)
357
+ }
358
+ })
359
+ })
360
+
361
+ const getPanelDynamicStyles = () => {
362
+ const { colors } = tokens
363
+ const t = (light: string, dark: string) =>
364
+ theme() === 'dark' ? dark : light
365
+ if (panelWidth() < secondBreakpoint) {
366
+ return css`
367
+ flex-direction: column;
368
+ background-color: ${t(colors.gray[300], colors.gray[600])};
369
+ `
370
+ }
371
+ return css`
372
+ flex-direction: row;
373
+ background-color: ${t(colors.gray[200], colors.darkGray[900])};
374
+ `
375
+ }
376
+
377
+ return (
378
+ <div
379
+ style={{
380
+ '--tsqd-font-size': '16px',
381
+ }}
382
+ ref={panelRef}
383
+ class={cx(
384
+ styles().parentPanel,
385
+ getPanelDynamicStyles(),
386
+ {
387
+ [css`
388
+ min-width: min-content;
389
+ `]: panelWidth() < thirdBreakpoint,
390
+ },
391
+ 'tsqd-main-panel',
392
+ )}
393
+ >
394
+ {props.children}
395
+ </div>
396
+ )
397
+ }
398
+
399
+ const DraggablePanel: Component<DevtoolsPanelProps> = (props) => {
551
400
  const theme = useTheme()
552
401
  const css = useQueryDevtoolsContext().shadowDOMTarget
553
402
  ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
@@ -740,18 +589,14 @@ const DevtoolsPanel: Component<DevtoolsPanelProps> = (props) => {
740
589
  >
741
590
  <ChevronDown />
742
591
  </button>
743
- <ContentView
744
- localStore={props.localStore}
745
- setLocalStore={props.setLocalStore}
746
- />
592
+ <ContentView {...props} />
747
593
  </aside>
748
594
  )
749
595
  }
750
596
 
751
- const ContentView: Component<DevtoolsPanelProps> = (props) => {
597
+ export const ContentView: Component<ContentViewProps> = (props) => {
752
598
  setupQueryCacheSubscription()
753
599
  setupMutationCacheSubscription()
754
-
755
600
  let containerRef!: HTMLDivElement
756
601
  const theme = useTheme()
757
602
  const css = useQueryDevtoolsContext().shadowDOMTarget
@@ -900,8 +745,12 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
900
745
  <button
901
746
  class={cx(styles().logo, 'tsqd-text-logo-container')}
902
747
  onClick={() => {
903
- if (!pip().pipWindow) {
748
+ if (!pip().pipWindow && !props.showPanelViewOnly) {
904
749
  props.setLocalStore('open', 'false')
750
+ return
751
+ }
752
+ if (props.onClose) {
753
+ props.onClose()
905
754
  }
906
755
  }}
907
756
  aria-label="Close Tanstack query devtools"
@@ -1118,7 +967,7 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
1118
967
  >
1119
968
  {offline() ? <Offline /> : <Wifi />}
1120
969
  </button>
1121
- <Show when={!pip().pipWindow}>
970
+ <Show when={!pip().pipWindow && !pip().disabled}>
1122
971
  <button
1123
972
  onClick={() => {
1124
973
  pip().requestPipWindow(
@@ -1167,90 +1016,92 @@ const ContentView: Component<DevtoolsPanelProps> = (props) => {
1167
1016
  >
1168
1017
  Settings
1169
1018
  </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
1019
+ <Show when={!props.showPanelViewOnly}>
1020
+ <DropdownMenu.Sub overlap gutter={8} shift={-4}>
1021
+ <DropdownMenu.SubTrigger
1190
1022
  class={cx(
1191
- styles().settingsMenu,
1192
- 'tsqd-settings-submenu',
1023
+ styles().settingsSubTrigger,
1024
+ 'tsqd-settings-menu-sub-trigger',
1025
+ 'tsqd-settings-menu-sub-trigger-position',
1193
1026
  )}
1194
1027
  >
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"
1028
+ <span>Position</span>
1029
+ <ChevronDown />
1030
+ </DropdownMenu.SubTrigger>
1031
+ <DropdownMenu.Portal
1032
+ ref={(el) => setComputedVariables(el as HTMLDivElement)}
1033
+ mount={
1034
+ pip().pipWindow
1035
+ ? pip().pipWindow!.document.body
1036
+ : document.body
1037
+ }
1038
+ >
1039
+ <DropdownMenu.SubContent
1242
1040
  class={cx(
1243
- styles().settingsSubButton,
1244
- 'tsqd-settings-menu-position-btn',
1245
- 'tsqd-settings-menu-position-btn-right',
1041
+ styles().settingsMenu,
1042
+ 'tsqd-settings-submenu',
1246
1043
  )}
1247
1044
  >
1248
- <span>Right</span>
1249
- <ArrowRight />
1250
- </DropdownMenu.Item>
1251
- </DropdownMenu.SubContent>
1252
- </DropdownMenu.Portal>
1253
- </DropdownMenu.Sub>
1045
+ <DropdownMenu.Item
1046
+ onSelect={() => {
1047
+ setDevtoolsPosition('top')
1048
+ }}
1049
+ as="button"
1050
+ class={cx(
1051
+ styles().settingsSubButton,
1052
+ 'tsqd-settings-menu-position-btn',
1053
+ 'tsqd-settings-menu-position-btn-top',
1054
+ )}
1055
+ >
1056
+ <span>Top</span>
1057
+ <ArrowUp />
1058
+ </DropdownMenu.Item>
1059
+ <DropdownMenu.Item
1060
+ onSelect={() => {
1061
+ setDevtoolsPosition('bottom')
1062
+ }}
1063
+ as="button"
1064
+ class={cx(
1065
+ styles().settingsSubButton,
1066
+ 'tsqd-settings-menu-position-btn',
1067
+ 'tsqd-settings-menu-position-btn-bottom',
1068
+ )}
1069
+ >
1070
+ <span>Bottom</span>
1071
+ <ArrowDown />
1072
+ </DropdownMenu.Item>
1073
+ <DropdownMenu.Item
1074
+ onSelect={() => {
1075
+ setDevtoolsPosition('left')
1076
+ }}
1077
+ as="button"
1078
+ class={cx(
1079
+ styles().settingsSubButton,
1080
+ 'tsqd-settings-menu-position-btn',
1081
+ 'tsqd-settings-menu-position-btn-left',
1082
+ )}
1083
+ >
1084
+ <span>Left</span>
1085
+ <ArrowLeft />
1086
+ </DropdownMenu.Item>
1087
+ <DropdownMenu.Item
1088
+ onSelect={() => {
1089
+ setDevtoolsPosition('right')
1090
+ }}
1091
+ as="button"
1092
+ class={cx(
1093
+ styles().settingsSubButton,
1094
+ 'tsqd-settings-menu-position-btn',
1095
+ 'tsqd-settings-menu-position-btn-right',
1096
+ )}
1097
+ >
1098
+ <span>Right</span>
1099
+ <ArrowRight />
1100
+ </DropdownMenu.Item>
1101
+ </DropdownMenu.SubContent>
1102
+ </DropdownMenu.Portal>
1103
+ </DropdownMenu.Sub>
1104
+ </Show>
1254
1105
  <DropdownMenu.Sub overlap gutter={8} shift={-4}>
1255
1106
  <DropdownMenu.SubTrigger
1256
1107
  class={cx(
@@ -1747,7 +1598,7 @@ const QueryStatus: Component<QueryStatusProps> = (props) => {
1747
1598
  return false
1748
1599
  }
1749
1600
  }
1750
- if (panelWidth() < thirdBreakpoint) {
1601
+ if (panelWidth() < secondBreakpoint) {
1751
1602
  return false
1752
1603
  }
1753
1604
 
@@ -2603,6 +2454,32 @@ const stylesFactory = (
2603
2454
  background: ${t(colors.gray[400], colors.darkGray[300])};
2604
2455
  }
2605
2456
  `,
2457
+ parentPanel: css`
2458
+ z-index: 9999;
2459
+ display: flex;
2460
+ height: 100%;
2461
+ gap: ${tokens.size[0.5]};
2462
+ & * {
2463
+ box-sizing: border-box;
2464
+ text-transform: none;
2465
+ }
2466
+
2467
+ & *::-webkit-scrollbar {
2468
+ width: 7px;
2469
+ }
2470
+
2471
+ & *::-webkit-scrollbar-track {
2472
+ background: transparent;
2473
+ }
2474
+
2475
+ & *::-webkit-scrollbar-thumb {
2476
+ background: ${t(colors.gray[300], colors.darkGray[200])};
2477
+ }
2478
+
2479
+ & *::-webkit-scrollbar-thumb:hover {
2480
+ background: ${t(colors.gray[400], colors.darkGray[300])};
2481
+ }
2482
+ `,
2606
2483
  'devtoolsBtn-position-bottom-right': css`
2607
2484
  bottom: 12px;
2608
2485
  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