diff --git a/docs/config.json b/docs/config.json
index 89eb47fd2a..3e77bad407 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -929,6 +929,10 @@
{
"label": "Shadow DOM",
"to": "framework/react/examples/shadow-dom"
+ },
+ {
+ "label": "Devtools Embedded Panel",
+ "to": "framework/react/examples/devtools-panel"
}
]
},
diff --git a/docs/framework/react/devtools.md b/docs/framework/react/devtools.md
index e9331622a3..2725130b96 100644
--- a/docs/framework/react/devtools.md
+++ b/docs/framework/react/devtools.md
@@ -83,7 +83,50 @@ function App() {
- The position of the React Query devtools panel
- `client?: QueryClient`,
- Use this to use a custom QueryClient. Otherwise, the one from the nearest context will be used.
-- `errorTypes?: { name: string; initializer: (query: Query) => TError}`
+- `errorTypes?: { name: string; initializer: (query: Query) => TError}[]`
+ - Use this to predefine some errors that can be triggered on your queries. Initializer will be called (with the specific query) when that error is toggled on from the UI. It must return an Error.
+- `styleNonce?: string`
+ - Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
+- `shadowDOMTarget?: ShadowRoot`
+ - Default behavior will apply the devtool's styles to the head tag within the DOM.
+ - Use this to pass a shadow DOM target to the devtools so that the styles will be applied within the shadow DOM instead of within the head tag in the light DOM.
+
+## Embedded Mode
+
+Embedded mode will show the development tools as a fixed element in your application, so you can use our panel in your own development tools.
+
+Place the following code as high in your React app as you can. The closer it is to the root of the page, the better it will work!
+
+```tsx
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
+
+function App() {
+ const [isOpen, setIsOpen] = React.useState(false)
+
+ return (
+
+ {/* The rest of your application */}
+ setIsOpen(!isOpen)}
+ >{`${isOpen ? 'Close' : 'Open'} the devtools panel`}
+ {isOpen && setIsOpen(false)} />}
+
+ )
+}
+```
+
+### Options
+
+- `style?: React.CSSProperties`
+ - Custom styles for the devtools panel
+ - Default: `{ height: '500px' }`
+ - Example: `{ height: '100%' }`
+ - Example: `{ height: '100%', width: '100%' }`
+- `onClose?: () => unknown`
+ - Callback function that is called when the devtools panel is closed
+- `client?: QueryClient`,
+ - Use this to use a custom QueryClient. Otherwise, the one from the nearest context will be used.
+- `errorTypes?: { name: string; initializer: (query: Query) => TError}[]`
- Use this to predefine some errors that can be triggered on your queries. Initializer will be called (with the specific query) when that error is toggled on from the UI. It must return an Error.
- `styleNonce?: string`
- Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
diff --git a/examples/react/devtools-panel/.eslintrc b/examples/react/devtools-panel/.eslintrc
new file mode 100644
index 0000000000..4e03b9e10b
--- /dev/null
+++ b/examples/react/devtools-panel/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": ["plugin:react/jsx-runtime", "plugin:react-hooks/recommended"]
+}
diff --git a/examples/react/devtools-panel/.gitignore b/examples/react/devtools-panel/.gitignore
new file mode 100644
index 0000000000..4673b022e5
--- /dev/null
+++ b/examples/react/devtools-panel/.gitignore
@@ -0,0 +1,27 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+pnpm-lock.yaml
+yarn.lock
+package-lock.json
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/examples/react/devtools-panel/README.md b/examples/react/devtools-panel/README.md
new file mode 100644
index 0000000000..1cf8892652
--- /dev/null
+++ b/examples/react/devtools-panel/README.md
@@ -0,0 +1,6 @@
+# Example
+
+To run this example:
+
+- `npm install`
+- `npm run dev`
diff --git a/examples/react/devtools-panel/index.html b/examples/react/devtools-panel/index.html
new file mode 100644
index 0000000000..204cab6a43
--- /dev/null
+++ b/examples/react/devtools-panel/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ TanStack Query React Devtools Panel Example App
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
diff --git a/examples/react/devtools-panel/package.json b/examples/react/devtools-panel/package.json
new file mode 100644
index 0000000000..f6614ffe28
--- /dev/null
+++ b/examples/react/devtools-panel/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@tanstack/query-example-react-devtools-panel",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tanstack/react-query": "^5.53.2",
+ "@tanstack/react-query-devtools": "^5.53.2",
+ "react": "19.0.0-rc-4c2e457c7c-20240522",
+ "react-dom": "19.0.0-rc-4c2e457c7c-20240522"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "5.3.3",
+ "vite": "^5.3.5"
+ }
+}
diff --git a/examples/react/devtools-panel/public/emblem-light.svg b/examples/react/devtools-panel/public/emblem-light.svg
new file mode 100644
index 0000000000..a58e69ad5e
--- /dev/null
+++ b/examples/react/devtools-panel/public/emblem-light.svg
@@ -0,0 +1,13 @@
+
+
+
+ emblem-light
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/react/devtools-panel/src/index.tsx b/examples/react/devtools-panel/src/index.tsx
new file mode 100644
index 0000000000..7278a000e3
--- /dev/null
+++ b/examples/react/devtools-panel/src/index.tsx
@@ -0,0 +1,58 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import {
+ QueryClient,
+ QueryClientProvider,
+ useQuery,
+} from '@tanstack/react-query'
+import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
+
+const queryClient = new QueryClient()
+
+export default function App() {
+ const [isOpen, setIsOpen] = React.useState(false)
+
+ return (
+
+
+ setIsOpen(!isOpen)}
+ >{`${isOpen ? 'Close' : 'Open'} the devtools panel`}
+ {isOpen && setIsOpen(false)} />}
+
+ )
+}
+
+function Example() {
+ const { isPending, error, data, isFetching } = useQuery({
+ queryKey: ['repoData'],
+ queryFn: async () => {
+ const response = await fetch(
+ 'https://api.github.com/repos/TanStack/query',
+ )
+ return await response.json()
+ },
+ })
+
+ if (isPending) return 'Loading...'
+
+ if (error) return 'An error has occurred: ' + error.message
+
+ return (
+
+
{data.full_name}
+
{data.description}
+
👀 {data.subscribers_count} {' '}
+
✨ {data.stargazers_count} {' '}
+
🍴 {data.forks_count}
+
{isFetching ? 'Updating...' : ''}
+
+ )
+}
+
+const rootElement = document.getElementById('root') as HTMLElement
+ReactDOM.createRoot(rootElement).render( )
diff --git a/examples/react/devtools-panel/tsconfig.json b/examples/react/devtools-panel/tsconfig.json
new file mode 100644
index 0000000000..23a8707ef4
--- /dev/null
+++ b/examples/react/devtools-panel/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src", "eslint.config.js"]
+}
diff --git a/examples/react/devtools-panel/vite.config.ts b/examples/react/devtools-panel/vite.config.ts
new file mode 100644
index 0000000000..9ffcc67574
--- /dev/null
+++ b/examples/react/devtools-panel/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/packages/query-devtools/src/Devtools.tsx b/packages/query-devtools/src/Devtools.tsx
index b04e4f3d25..73802c0adb 100644
--- a/packages/query-devtools/src/Devtools.tsx
+++ b/packages/query-devtools/src/Devtools.tsx
@@ -2,30 +2,26 @@ import {
For,
Show,
batch,
- createContext,
createEffect,
createMemo,
createSignal,
on,
onCleanup,
onMount,
- useContext,
} from 'solid-js'
import { rankItem } from '@tanstack/match-sorter-utils'
import * as goober from 'goober'
import { clsx as cx } from 'clsx'
import { TransitionGroup } from 'solid-transition-group'
import { Key } from '@solid-primitives/keyed'
-import { createLocalStorage } from '@solid-primitives/storage'
import { createResizeObserver } from '@solid-primitives/resize-observer'
import { DropdownMenu, RadioGroup } from '@kobalte/core'
-import { Portal, clearDelegatedEvents, delegateEvents } from 'solid-js/web'
+import { Portal } from 'solid-js/web'
import { tokens } from './theme'
import {
convertRemToPixels,
displayValue,
getMutationStatusColor,
- getPreferredColorScheme,
getQueryStatusColor,
getQueryStatusColorByLabel,
getQueryStatusLabel,
@@ -55,18 +51,25 @@ import {
XCircle,
} from './icons'
import Explorer from './Explorer'
+import { usePiPWindow, useQueryDevtoolsContext, useTheme } from './contexts'
import {
- QueryDevtoolsContext,
- ThemeContext,
- useQueryDevtoolsContext,
- useTheme,
-} from './Context'
+ BUTTON_POSITION,
+ DEFAULT_HEIGHT,
+ DEFAULT_MUTATION_SORT_FN_NAME,
+ DEFAULT_SORT_FN_NAME,
+ DEFAULT_SORT_ORDER,
+ DEFAULT_WIDTH,
+ INITIAL_IS_OPEN,
+ POSITION,
+ firstBreakpoint,
+ secondBreakpoint,
+ thirdBreakpoint,
+} from './constants'
import type {
- DevtoolsButtonPosition,
DevtoolsErrorType,
DevtoolsPosition,
QueryDevtoolsProps,
-} from './Context'
+} from './contexts'
import type {
Mutation,
MutationCache,
@@ -83,26 +86,19 @@ interface DevtoolsPanelProps {
setLocalStore: StorageSetter
}
+interface ContentViewProps {
+ localStore: StorageObject
+ setLocalStore: StorageSetter
+ showPanelViewOnly?: boolean
+ onClose?: () => unknown
+}
+
interface QueryStatusProps {
label: string
color: 'green' | 'yellow' | 'gray' | 'blue' | 'purple' | 'red'
count: number
}
-const firstBreakpoint = 1024
-const secondBreakpoint = 796
-const thirdBreakpoint = 700
-
-const BUTTON_POSITION: DevtoolsButtonPosition = 'bottom-right'
-const POSITION: DevtoolsPosition = 'bottom'
-const THEME_PREFERENCE = 'system'
-const INITIAL_IS_OPEN = false
-const DEFAULT_HEIGHT = 500
-const DEFAULT_WIDTH = 500
-const DEFAULT_SORT_FN_NAME = Object.keys(sortFns)[0]
-const DEFAULT_SORT_ORDER = 1
-const DEFAULT_MUTATION_SORT_FN_NAME = Object.keys(mutationSortFns)[0]
-
const [selectedQueryHash, setSelectedQueryHash] = createSignal(
null,
)
@@ -116,211 +112,7 @@ export type DevtoolsComponentType = Component & {
shadowDOMTarget?: ShadowRoot
}
-interface PiPProviderProps {
- children: JSX.Element
- localStore: StorageObject
- setLocalStore: StorageSetter
-}
-
-type PiPContextType = {
- pipWindow: Window | null
- requestPipWindow: (width: number, height: number) => Promise
- closePipWindow: () => void
-}
-
-const PiPContext = createContext | undefined>(
- undefined,
-)
-
-const PiPProvider = (props: PiPProviderProps) => {
- // Expose pipWindow that is currently active
- const [pipWindow, setPipWindow] = createSignal(null)
-
- // Close pipWindow programmatically
- const closePipWindow = () => {
- const w = pipWindow()
- if (w != null) {
- w.close()
- setPipWindow(null)
- }
- }
-
- // Open new pipWindow
- const requestPipWindow = async (width: number, height: number) => {
- // We don't want to allow multiple requests.
- if (pipWindow() != null) {
- return
- }
-
- const pip = window.open(
- '',
- 'TSQD-Devtools-Panel',
- `width=${width},height=${height},popup`,
- )
-
- if (!pip) {
- throw new Error(
- 'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.',
- )
- }
-
- // Remove existing styles
- pip.document.head.innerHTML = ''
- // Remove existing body
- pip.document.body.innerHTML = ''
- // Clear Delegated Events
- clearDelegatedEvents(pip.document)
-
- pip.document.title = 'TanStack Query Devtools'
- pip.document.body.style.margin = '0'
-
- // Detect when window is closed by user
- pip.addEventListener('pagehide', () => {
- props.setLocalStore('pip_open', 'false')
- setPipWindow(null)
- })
-
- // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all
- // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
- ;[
- ...(useQueryDevtoolsContext().shadowDOMTarget || document).styleSheets,
- ].forEach((styleSheet) => {
- try {
- const cssRules = [...styleSheet.cssRules]
- .map((rule) => rule.cssText)
- .join('')
- const style = document.createElement('style')
- const style_node = styleSheet.ownerNode
- let style_id = ''
-
- if (style_node && 'id' in style_node) {
- style_id = style_node.id
- }
-
- if (style_id) {
- style.setAttribute('id', style_id)
- }
- style.textContent = cssRules
- pip.document.head.appendChild(style)
- } catch (e) {
- const link = document.createElement('link')
- if (styleSheet.href == null) {
- return
- }
-
- link.rel = 'stylesheet'
- link.type = styleSheet.type
- link.media = styleSheet.media.toString()
- link.href = styleSheet.href
- pip.document.head.appendChild(link)
- }
- })
- delegateEvents(
- [
- 'focusin',
- 'focusout',
- 'pointermove',
- 'keydown',
- 'pointerdown',
- 'pointerup',
- 'click',
- 'mousedown',
- 'input',
- ],
- pip.document,
- )
- props.setLocalStore('pip_open', 'true')
- setPipWindow(pip)
- }
-
- createEffect(() => {
- const pip_open = (props.localStore.pip_open ?? 'false') as 'true' | 'false'
- if (pip_open === 'true') {
- requestPipWindow(
- Number(window.innerWidth),
- Number(props.localStore.height || DEFAULT_HEIGHT),
- )
- }
- })
-
- createEffect(() => {
- // Setup mutation observer for goober styles with id `_goober
- const gooberStyles = (
- useQueryDevtoolsContext().shadowDOMTarget || document
- ).querySelector('#_goober')
- const w = pipWindow()
- if (gooberStyles && w) {
- const observer = new MutationObserver(() => {
- const pip_style = (
- useQueryDevtoolsContext().shadowDOMTarget || w.document
- ).querySelector('#_goober')
- if (pip_style) {
- pip_style.textContent = gooberStyles.textContent
- }
- })
- observer.observe(gooberStyles, {
- childList: true, // observe direct children
- subtree: true, // and lower descendants too
- characterDataOldValue: true, // pass old data to callback
- })
- onCleanup(() => {
- observer.disconnect()
- })
- }
- })
-
- const value = createMemo(() => ({
- pipWindow: pipWindow(),
- requestPipWindow,
- closePipWindow,
- }))
-
- return (
- {props.children}
- )
-}
-
-const usePiPWindow = () => {
- const context = createMemo(() => {
- const ctx = useContext(PiPContext)
- if (!ctx) {
- throw new Error('usePiPWindow must be used within a PiPProvider')
- }
- return ctx()
- })
- return context
-}
-
-const DevtoolsComponent: DevtoolsComponentType = (props) => {
- const [localStore, setLocalStore] = createLocalStorage({
- prefix: 'TanstackQueryDevtools',
- })
-
- const colorScheme = getPreferredColorScheme()
-
- const theme = createMemo(() => {
- const preference = (localStore.theme_preference || THEME_PREFERENCE) as
- | 'system'
- | 'dark'
- | 'light'
- if (preference !== 'system') return preference
- return colorScheme()
- })
-
- return (
-
-
-
-
-
-
-
- )
-}
-
-export default DevtoolsComponent
-
-const Devtools: Component = (props) => {
+export const Devtools: Component = (props) => {
const theme = useTheme()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
@@ -393,10 +185,7 @@ const Devtools: Component = (props) => {
-
+
@@ -444,7 +233,7 @@ const Devtools: Component = (props) => {
>
-
@@ -547,7 +336,66 @@ const PiPPanel: Component<{
)
}
-const DevtoolsPanel: Component = (props) => {
+export const ParentPanel: Component<{
+ children: JSX.Element
+}> = (props) => {
+ const theme = useTheme()
+ const css = useQueryDevtoolsContext().shadowDOMTarget
+ ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
+ : goober.css
+ const styles = createMemo(() => {
+ return theme() === 'dark' ? darkStyles(css) : lightStyles(css)
+ })
+
+ let panelRef!: HTMLDivElement
+
+ onMount(() => {
+ createResizeObserver(panelRef, ({ width }, el) => {
+ if (el === panelRef) {
+ setPanelWidth(width)
+ }
+ })
+ })
+
+ const getPanelDynamicStyles = () => {
+ const { colors } = tokens
+ const t = (light: string, dark: string) =>
+ theme() === 'dark' ? dark : light
+ if (panelWidth() < secondBreakpoint) {
+ return css`
+ flex-direction: column;
+ background-color: ${t(colors.gray[300], colors.gray[600])};
+ `
+ }
+ return css`
+ flex-direction: row;
+ background-color: ${t(colors.gray[200], colors.darkGray[900])};
+ `
+ }
+
+ return (
+
+ {props.children}
+
+ )
+}
+
+const DraggablePanel: Component = (props) => {
const theme = useTheme()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
@@ -740,18 +588,14 @@ const DevtoolsPanel: Component = (props) => {
>
-
+
)
}
-const ContentView: Component = (props) => {
+export const ContentView: Component = (props) => {
setupQueryCacheSubscription()
setupMutationCacheSubscription()
-
let containerRef!: HTMLDivElement
const theme = useTheme()
const css = useQueryDevtoolsContext().shadowDOMTarget
@@ -900,8 +744,12 @@ const ContentView: Component = (props) => {
{
- if (!pip().pipWindow) {
+ if (!pip().pipWindow && !props.showPanelViewOnly) {
props.setLocalStore('open', 'false')
+ return
+ }
+ if (props.onClose) {
+ props.onClose()
}
}}
aria-label="Close Tanstack query devtools"
@@ -1118,7 +966,7 @@ const ContentView: Component = (props) => {
>
{offline() ? : }
-
+
{
pip().requestPipWindow(
@@ -1167,90 +1015,92 @@ const ContentView: Component = (props) => {
>
Settings
-
-
- Position
-
-
- setComputedVariables(el as HTMLDivElement)}
- mount={
- pip().pipWindow
- ? pip().pipWindow!.document.body
- : document.body
- }
- >
-
+
+
- {
- setDevtoolsPosition('top')
- }}
- as="button"
- class={cx(
- styles().settingsSubButton,
- 'tsqd-settings-menu-position-btn',
- 'tsqd-settings-menu-position-btn-top',
- )}
- >
- Top
-
-
- {
- setDevtoolsPosition('bottom')
- }}
- as="button"
- class={cx(
- styles().settingsSubButton,
- 'tsqd-settings-menu-position-btn',
- 'tsqd-settings-menu-position-btn-bottom',
- )}
- >
- Bottom
-
-
- {
- setDevtoolsPosition('left')
- }}
- as="button"
- class={cx(
- styles().settingsSubButton,
- 'tsqd-settings-menu-position-btn',
- 'tsqd-settings-menu-position-btn-left',
- )}
- >
- Left
-
-
- {
- setDevtoolsPosition('right')
- }}
- as="button"
+ Position
+
+
+ setComputedVariables(el as HTMLDivElement)}
+ mount={
+ pip().pipWindow
+ ? pip().pipWindow!.document.body
+ : document.body
+ }
+ >
+
- Right
-
-
-
-
-
+ {
+ setDevtoolsPosition('top')
+ }}
+ as="button"
+ class={cx(
+ styles().settingsSubButton,
+ 'tsqd-settings-menu-position-btn',
+ 'tsqd-settings-menu-position-btn-top',
+ )}
+ >
+ Top
+
+
+ {
+ setDevtoolsPosition('bottom')
+ }}
+ as="button"
+ class={cx(
+ styles().settingsSubButton,
+ 'tsqd-settings-menu-position-btn',
+ 'tsqd-settings-menu-position-btn-bottom',
+ )}
+ >
+ Bottom
+
+
+ {
+ setDevtoolsPosition('left')
+ }}
+ as="button"
+ class={cx(
+ styles().settingsSubButton,
+ 'tsqd-settings-menu-position-btn',
+ 'tsqd-settings-menu-position-btn-left',
+ )}
+ >
+ Left
+
+
+ {
+ setDevtoolsPosition('right')
+ }}
+ as="button"
+ class={cx(
+ styles().settingsSubButton,
+ 'tsqd-settings-menu-position-btn',
+ 'tsqd-settings-menu-position-btn-right',
+ )}
+ >
+ Right
+
+
+
+
+
+
= (props) => {
return false
}
}
- if (panelWidth() < thirdBreakpoint) {
+ if (panelWidth() < secondBreakpoint) {
return false
}
@@ -2603,6 +2453,32 @@ const stylesFactory = (
background: ${t(colors.gray[400], colors.darkGray[300])};
}
`,
+ parentPanel: css`
+ z-index: 9999;
+ display: flex;
+ height: 100%;
+ gap: ${tokens.size[0.5]};
+ & * {
+ box-sizing: border-box;
+ text-transform: none;
+ }
+
+ & *::-webkit-scrollbar {
+ width: 7px;
+ }
+
+ & *::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ & *::-webkit-scrollbar-thumb {
+ background: ${t(colors.gray[300], colors.darkGray[200])};
+ }
+
+ & *::-webkit-scrollbar-thumb:hover {
+ background: ${t(colors.gray[400], colors.darkGray[300])};
+ }
+ `,
'devtoolsBtn-position-bottom-right': css`
bottom: 12px;
right: 12px;
diff --git a/packages/query-devtools/src/DevtoolsComponent.tsx b/packages/query-devtools/src/DevtoolsComponent.tsx
new file mode 100644
index 0000000000..45d93204e0
--- /dev/null
+++ b/packages/query-devtools/src/DevtoolsComponent.tsx
@@ -0,0 +1,36 @@
+import { createLocalStorage } from '@solid-primitives/storage'
+import { createMemo } from 'solid-js'
+import { Devtools } from './Devtools'
+import { getPreferredColorScheme } from './utils'
+import { THEME_PREFERENCE } from './constants'
+import { PiPProvider, QueryDevtoolsContext, ThemeContext } from './contexts'
+import type { DevtoolsComponentType } from './Devtools'
+
+const DevtoolsComponent: DevtoolsComponentType = (props) => {
+ const [localStore, setLocalStore] = createLocalStorage({
+ prefix: 'TanstackQueryDevtools',
+ })
+
+ const colorScheme = getPreferredColorScheme()
+
+ const theme = createMemo(() => {
+ const preference = (localStore.theme_preference || THEME_PREFERENCE) as
+ | 'system'
+ | 'dark'
+ | 'light'
+ if (preference !== 'system') return preference
+ return colorScheme()
+ })
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export default DevtoolsComponent
diff --git a/packages/query-devtools/src/DevtoolsPanelComponent.tsx b/packages/query-devtools/src/DevtoolsPanelComponent.tsx
new file mode 100644
index 0000000000..eb3d19e4d9
--- /dev/null
+++ b/packages/query-devtools/src/DevtoolsPanelComponent.tsx
@@ -0,0 +1,47 @@
+import { createLocalStorage } from '@solid-primitives/storage'
+import { createMemo } from 'solid-js'
+import { ContentView, ParentPanel } from './Devtools'
+import { getPreferredColorScheme } from './utils'
+import { THEME_PREFERENCE } from './constants'
+import { PiPProvider, QueryDevtoolsContext, ThemeContext } from './contexts'
+import type { DevtoolsComponentType } from './Devtools'
+
+const DevtoolsPanelComponent: DevtoolsComponentType = (props) => {
+ const [localStore, setLocalStore] = createLocalStorage({
+ prefix: 'TanstackQueryDevtools',
+ })
+
+ const colorScheme = getPreferredColorScheme()
+
+ const theme = createMemo(() => {
+ const preference = (localStore.theme_preference || THEME_PREFERENCE) as
+ | 'system'
+ | 'dark'
+ | 'light'
+ if (preference !== 'system') return preference
+ return colorScheme()
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default DevtoolsPanelComponent
diff --git a/packages/query-devtools/src/Explorer.tsx b/packages/query-devtools/src/Explorer.tsx
index a028ff9540..923eb016b3 100644
--- a/packages/query-devtools/src/Explorer.tsx
+++ b/packages/query-devtools/src/Explorer.tsx
@@ -10,7 +10,7 @@ import {
updateNestedDataByPath,
} from './utils'
import { Check, CopiedCopier, Copier, ErrorCopier, List, Trash } from './icons'
-import { useQueryDevtoolsContext, useTheme } from './Context'
+import { useQueryDevtoolsContext, useTheme } from './contexts'
import type { Query } from '@tanstack/query-core'
/**
diff --git a/packages/query-devtools/src/index.tsx b/packages/query-devtools/src/TanstackQueryDevtools.tsx
similarity index 96%
rename from packages/query-devtools/src/index.tsx
rename to packages/query-devtools/src/TanstackQueryDevtools.tsx
index 8520987417..4b14c083f2 100644
--- a/packages/query-devtools/src/index.tsx
+++ b/packages/query-devtools/src/TanstackQueryDevtools.tsx
@@ -11,10 +11,9 @@ import type {
DevtoolsErrorType,
DevtoolsPosition,
QueryDevtoolsProps,
-} from './Context'
+} from './contexts'
import type { Signal } from 'solid-js'
-export type { DevtoolsButtonPosition, DevtoolsPosition, DevtoolsErrorType }
export interface TanstackQueryDevtoolsConfig extends QueryDevtoolsProps {
styleNonce?: string
shadowDOMTarget?: ShadowRoot
@@ -95,7 +94,7 @@ class TanstackQueryDevtools {
if (this.#Component) {
Devtools = this.#Component
} else {
- Devtools = lazy(() => import('./Devtools'))
+ Devtools = lazy(() => import('./DevtoolsComponent'))
this.#Component = Devtools
}
diff --git a/packages/query-devtools/src/TanstackQueryDevtoolsPanel.tsx b/packages/query-devtools/src/TanstackQueryDevtoolsPanel.tsx
new file mode 100644
index 0000000000..bf26198854
--- /dev/null
+++ b/packages/query-devtools/src/TanstackQueryDevtoolsPanel.tsx
@@ -0,0 +1,153 @@
+import { render } from 'solid-js/web'
+import { createSignal, lazy } from 'solid-js'
+import { setupStyleSheet } from './utils'
+import type {
+ QueryClient,
+ onlineManager as TOnlineManager,
+} from '@tanstack/query-core'
+import type { DevtoolsComponentType } from './Devtools'
+import type {
+ DevtoolsButtonPosition,
+ DevtoolsErrorType,
+ DevtoolsPosition,
+ QueryDevtoolsProps,
+} from './contexts'
+import type { Signal } from 'solid-js'
+
+export interface TanstackQueryDevtoolsPanelConfig extends QueryDevtoolsProps {
+ styleNonce?: string
+ shadowDOMTarget?: ShadowRoot
+ onClose?: () => unknown
+}
+
+class TanstackQueryDevtoolsPanel {
+ #client: Signal
+ #onlineManager: typeof TOnlineManager
+ #queryFlavor: string
+ #version: string
+ #isMounted = false
+ #styleNonce?: string
+ #shadowDOMTarget?: ShadowRoot
+ #buttonPosition: Signal
+ #position: Signal
+ #initialIsOpen: Signal
+ #errorTypes: Signal | undefined>
+ #onClose: Signal<(() => unknown) | undefined>
+ #Component: DevtoolsComponentType | undefined
+ #dispose?: () => void
+
+ constructor(config: TanstackQueryDevtoolsPanelConfig) {
+ const {
+ client,
+ queryFlavor,
+ version,
+ onlineManager,
+ buttonPosition,
+ position,
+ initialIsOpen,
+ errorTypes,
+ styleNonce,
+ shadowDOMTarget,
+ onClose,
+ } = config
+ this.#client = createSignal(client)
+ this.#queryFlavor = queryFlavor
+ this.#version = version
+ this.#onlineManager = onlineManager
+ this.#styleNonce = styleNonce
+ this.#shadowDOMTarget = shadowDOMTarget
+ this.#buttonPosition = createSignal(buttonPosition)
+ this.#position = createSignal(position)
+ this.#initialIsOpen = createSignal(initialIsOpen)
+ this.#errorTypes = createSignal(errorTypes)
+ this.#onClose = createSignal(onClose)
+ }
+
+ setButtonPosition(position: DevtoolsButtonPosition) {
+ this.#buttonPosition[1](position)
+ }
+
+ setPosition(position: DevtoolsPosition) {
+ this.#position[1](position)
+ }
+
+ setInitialIsOpen(isOpen: boolean) {
+ this.#initialIsOpen[1](isOpen)
+ }
+
+ setErrorTypes(errorTypes: Array) {
+ this.#errorTypes[1](errorTypes)
+ }
+
+ setClient(client: QueryClient) {
+ this.#client[1](client)
+ }
+
+ setOnClose(onClose: () => unknown) {
+ this.#onClose[1](() => onClose)
+ }
+
+ mount(el: T) {
+ if (this.#isMounted) {
+ throw new Error('Devtools is already mounted')
+ }
+ const dispose = render(() => {
+ const [btnPosition] = this.#buttonPosition
+ const [pos] = this.#position
+ const [isOpen] = this.#initialIsOpen
+ const [errors] = this.#errorTypes
+ const [queryClient] = this.#client
+ const [onClose] = this.#onClose
+ let Devtools: DevtoolsComponentType
+
+ if (this.#Component) {
+ Devtools = this.#Component
+ } else {
+ Devtools = lazy(() => import('./DevtoolsPanelComponent'))
+ this.#Component = Devtools
+ }
+
+ setupStyleSheet(this.#styleNonce, this.#shadowDOMTarget)
+ return (
+
+ )
+ }, el)
+ this.#isMounted = true
+ this.#dispose = dispose
+ }
+
+ unmount() {
+ if (!this.#isMounted) {
+ throw new Error('Devtools is not mounted')
+ }
+ this.#dispose?.()
+ this.#isMounted = false
+ }
+}
+
+export { TanstackQueryDevtoolsPanel }
diff --git a/packages/query-devtools/src/constants.ts b/packages/query-devtools/src/constants.ts
new file mode 100644
index 0000000000..b4c0c22506
--- /dev/null
+++ b/packages/query-devtools/src/constants.ts
@@ -0,0 +1,17 @@
+import { mutationSortFns, sortFns } from './utils'
+import type { DevtoolsButtonPosition, DevtoolsPosition } from './contexts'
+
+export const firstBreakpoint = 1024
+export const secondBreakpoint = 796
+export const thirdBreakpoint = 700
+
+export const BUTTON_POSITION: DevtoolsButtonPosition = 'bottom-right'
+export const POSITION: DevtoolsPosition = 'bottom'
+export const THEME_PREFERENCE = 'system'
+export const INITIAL_IS_OPEN = false
+export const DEFAULT_HEIGHT = 500
+export const PIP_DEFAULT_HEIGHT = 500
+export const DEFAULT_WIDTH = 500
+export const DEFAULT_SORT_FN_NAME = Object.keys(sortFns)[0]
+export const DEFAULT_SORT_ORDER = 1
+export const DEFAULT_MUTATION_SORT_FN_NAME = Object.keys(mutationSortFns)[0]
diff --git a/packages/query-devtools/src/contexts/PiPContext.tsx b/packages/query-devtools/src/contexts/PiPContext.tsx
new file mode 100644
index 0000000000..378f469738
--- /dev/null
+++ b/packages/query-devtools/src/contexts/PiPContext.tsx
@@ -0,0 +1,191 @@
+import {
+ createContext,
+ createEffect,
+ createMemo,
+ createSignal,
+ onCleanup,
+ useContext,
+} from 'solid-js'
+import { clearDelegatedEvents, delegateEvents } from 'solid-js/web'
+import { PIP_DEFAULT_HEIGHT } from '../constants'
+import { useQueryDevtoolsContext } from './QueryDevtoolsContext'
+import type { Accessor, JSX } from 'solid-js'
+import type { StorageObject, StorageSetter } from '@solid-primitives/storage'
+
+interface PiPProviderProps {
+ children: JSX.Element
+ localStore: StorageObject
+ setLocalStore: StorageSetter
+ disabled?: boolean
+}
+
+type PiPContextType = {
+ pipWindow: Window | null
+ requestPipWindow: (width: number, height: number) => void
+ closePipWindow: () => void
+ disabled: boolean
+}
+
+const PiPContext = createContext | undefined>(
+ undefined,
+)
+
+export const PiPProvider = (props: PiPProviderProps) => {
+ // Expose pipWindow that is currently active
+ const [pipWindow, setPipWindow] = createSignal(null)
+
+ // Close pipWindow programmatically
+ const closePipWindow = () => {
+ const w = pipWindow()
+ if (w != null) {
+ w.close()
+ setPipWindow(null)
+ }
+ }
+
+ // Open new pipWindow
+ const requestPipWindow = (width: number, height: number) => {
+ // We don't want to allow multiple requests.
+ if (pipWindow() != null) {
+ return
+ }
+
+ const pip = window.open(
+ '',
+ 'TSQD-Devtools-Panel',
+ `width=${width},height=${height},popup`,
+ )
+
+ if (!pip) {
+ throw new Error(
+ 'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.',
+ )
+ }
+
+ // Remove existing styles
+ pip.document.head.innerHTML = ''
+ // Remove existing body
+ pip.document.body.innerHTML = ''
+ // Clear Delegated Events
+ clearDelegatedEvents(pip.document)
+
+ pip.document.title = 'TanStack Query Devtools'
+ pip.document.body.style.margin = '0'
+
+ // Detect when window is closed by user
+ pip.addEventListener('pagehide', () => {
+ props.setLocalStore('pip_open', 'false')
+ setPipWindow(null)
+ })
+
+ // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all
+ // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
+ ;[
+ ...(useQueryDevtoolsContext().shadowDOMTarget || document).styleSheets,
+ ].forEach((styleSheet) => {
+ try {
+ const cssRules = [...styleSheet.cssRules]
+ .map((rule) => rule.cssText)
+ .join('')
+ const style = document.createElement('style')
+ const style_node = styleSheet.ownerNode
+ let style_id = ''
+
+ if (style_node && 'id' in style_node) {
+ style_id = style_node.id
+ }
+
+ if (style_id) {
+ style.setAttribute('id', style_id)
+ }
+ style.textContent = cssRules
+ pip.document.head.appendChild(style)
+ } catch (e) {
+ const link = document.createElement('link')
+ if (styleSheet.href == null) {
+ return
+ }
+
+ link.rel = 'stylesheet'
+ link.type = styleSheet.type
+ link.media = styleSheet.media.toString()
+ link.href = styleSheet.href
+ pip.document.head.appendChild(link)
+ }
+ })
+ delegateEvents(
+ [
+ 'focusin',
+ 'focusout',
+ 'pointermove',
+ 'keydown',
+ 'pointerdown',
+ 'pointerup',
+ 'click',
+ 'mousedown',
+ 'input',
+ ],
+ pip.document,
+ )
+ props.setLocalStore('pip_open', 'true')
+ setPipWindow(pip)
+ }
+
+ createEffect(() => {
+ const pip_open = (props.localStore.pip_open ?? 'false') as 'true' | 'false'
+ if (pip_open === 'true' && !props.disabled) {
+ requestPipWindow(
+ Number(window.innerWidth),
+ Number(props.localStore.height || PIP_DEFAULT_HEIGHT),
+ )
+ }
+ })
+
+ createEffect(() => {
+ // Setup mutation observer for goober styles with id `_goober
+ const gooberStyles = (
+ useQueryDevtoolsContext().shadowDOMTarget || document
+ ).querySelector('#_goober')
+ const w = pipWindow()
+ if (gooberStyles && w) {
+ const observer = new MutationObserver(() => {
+ const pip_style = (
+ useQueryDevtoolsContext().shadowDOMTarget || w.document
+ ).querySelector('#_goober')
+ if (pip_style) {
+ pip_style.textContent = gooberStyles.textContent
+ }
+ })
+ observer.observe(gooberStyles, {
+ childList: true, // observe direct children
+ subtree: true, // and lower descendants too
+ characterDataOldValue: true, // pass old data to callback
+ })
+ onCleanup(() => {
+ observer.disconnect()
+ })
+ }
+ })
+
+ const value = createMemo(() => ({
+ pipWindow: pipWindow(),
+ requestPipWindow,
+ closePipWindow,
+ disabled: props.disabled ?? false,
+ }))
+
+ return (
+ {props.children}
+ )
+}
+
+export const usePiPWindow = () => {
+ const context = createMemo(() => {
+ const ctx = useContext(PiPContext)
+ if (!ctx) {
+ throw new Error('usePiPWindow must be used within a PiPProvider')
+ }
+ return ctx()
+ })
+ return context
+}
diff --git a/packages/query-devtools/src/Context.ts b/packages/query-devtools/src/contexts/QueryDevtoolsContext.ts
similarity index 85%
rename from packages/query-devtools/src/Context.ts
rename to packages/query-devtools/src/contexts/QueryDevtoolsContext.ts
index 969e1e2bc0..5af5ba3f7d 100644
--- a/packages/query-devtools/src/Context.ts
+++ b/packages/query-devtools/src/contexts/QueryDevtoolsContext.ts
@@ -1,5 +1,4 @@
import { createContext, useContext } from 'solid-js'
-import type { Accessor } from 'solid-js'
import type { Query, QueryClient, onlineManager } from '@tanstack/query-core'
type XPosition = 'left' | 'right'
@@ -29,6 +28,7 @@ export interface QueryDevtoolsProps {
initialIsOpen?: boolean
errorTypes?: Array
shadowDOMTarget?: ShadowRoot
+ onClose?: () => unknown
}
export const QueryDevtoolsContext = createContext({
@@ -42,11 +42,3 @@ export const QueryDevtoolsContext = createContext({
export function useQueryDevtoolsContext() {
return useContext(QueryDevtoolsContext)
}
-
-export const ThemeContext = createContext>(
- () => 'dark' as const,
-)
-
-export function useTheme() {
- return useContext(ThemeContext)
-}
diff --git a/packages/query-devtools/src/contexts/ThemeContext.ts b/packages/query-devtools/src/contexts/ThemeContext.ts
new file mode 100644
index 0000000000..dfb286341f
--- /dev/null
+++ b/packages/query-devtools/src/contexts/ThemeContext.ts
@@ -0,0 +1,10 @@
+import { createContext, useContext } from 'solid-js'
+import type { Accessor } from 'solid-js'
+
+export const ThemeContext = createContext>(
+ () => 'dark' as const,
+)
+
+export function useTheme() {
+ return useContext(ThemeContext)
+}
diff --git a/packages/query-devtools/src/contexts/index.ts b/packages/query-devtools/src/contexts/index.ts
new file mode 100644
index 0000000000..b9329a3cd9
--- /dev/null
+++ b/packages/query-devtools/src/contexts/index.ts
@@ -0,0 +1,3 @@
+export * from './PiPContext'
+export * from './QueryDevtoolsContext'
+export * from './ThemeContext'
diff --git a/packages/query-devtools/src/index.ts b/packages/query-devtools/src/index.ts
new file mode 100644
index 0000000000..1cf3cedd17
--- /dev/null
+++ b/packages/query-devtools/src/index.ts
@@ -0,0 +1,13 @@
+export type {
+ DevtoolsButtonPosition,
+ DevtoolsErrorType,
+ DevtoolsPosition,
+} from './contexts'
+export {
+ TanstackQueryDevtools,
+ type TanstackQueryDevtoolsConfig,
+} from './TanstackQueryDevtools'
+export {
+ TanstackQueryDevtoolsPanel,
+ type TanstackQueryDevtoolsPanelConfig,
+} from './TanstackQueryDevtoolsPanel'
diff --git a/packages/query-devtools/src/utils.tsx b/packages/query-devtools/src/utils.tsx
index 6159fcd63e..9a826cd21e 100644
--- a/packages/query-devtools/src/utils.tsx
+++ b/packages/query-devtools/src/utils.tsx
@@ -1,7 +1,7 @@
import { serialize } from 'superjson'
import { createSignal, onCleanup, onMount } from 'solid-js'
import type { Mutation, Query } from '@tanstack/query-core'
-import type { DevtoolsPosition } from './Context'
+import type { DevtoolsPosition } from './contexts'
export function getQueryStatusLabel(query: Query) {
return query.state.fetchStatus === 'fetching'
diff --git a/packages/query-devtools/tsup.config.js b/packages/query-devtools/tsup.config.js
index 1ddd3f74f0..fac53914a6 100644
--- a/packages/query-devtools/tsup.config.js
+++ b/packages/query-devtools/tsup.config.js
@@ -5,7 +5,7 @@ import { generateTsupOptions, parsePresetOptions } from 'tsup-preset-solid'
const preset_options = {
entries: {
- entry: 'src/index.tsx',
+ entry: 'src/index.ts',
dev_entry: true,
},
cjs: true,
diff --git a/packages/react-query-devtools/src/devtools.tsx b/packages/react-query-devtools/src/ReactQueryDevtools.tsx
similarity index 100%
rename from packages/react-query-devtools/src/devtools.tsx
rename to packages/react-query-devtools/src/ReactQueryDevtools.tsx
diff --git a/packages/react-query-devtools/src/ReactQueryDevtoolsPanel.tsx b/packages/react-query-devtools/src/ReactQueryDevtoolsPanel.tsx
new file mode 100644
index 0000000000..86afd0ba85
--- /dev/null
+++ b/packages/react-query-devtools/src/ReactQueryDevtoolsPanel.tsx
@@ -0,0 +1,91 @@
+'use client'
+import * as React from 'react'
+import { onlineManager, useQueryClient } from '@tanstack/react-query'
+import { TanstackQueryDevtoolsPanel } from '@tanstack/query-devtools'
+import type { DevtoolsErrorType } from '@tanstack/query-devtools'
+import type { QueryClient } from '@tanstack/react-query'
+
+export interface DevtoolsPanelOptions {
+ /**
+ * Custom instance of QueryClient
+ */
+ client?: QueryClient
+ /**
+ * Use this so you can define custom errors that can be shown in the devtools.
+ */
+ errorTypes?: Array
+ /**
+ * Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
+ */
+ styleNonce?: string
+ /**
+ * Use this so you can attach the devtool's styles to specific element in the DOM.
+ */
+ shadowDOMTarget?: ShadowRoot
+
+ /**
+ * Custom styles for the devtools panel
+ * @default { height: '500px' }
+ * @example { height: '100%' }
+ * @example { height: '100%', width: '100%' }
+ */
+ style?: React.CSSProperties
+
+ /**
+ * Callback function that is called when the devtools panel is closed
+ */
+ onClose?: () => unknown
+}
+
+export function ReactQueryDevtoolsPanel(
+ props: DevtoolsPanelOptions,
+): React.ReactElement | null {
+ const queryClient = useQueryClient(props.client)
+ const ref = React.useRef(null)
+ const { errorTypes, styleNonce, shadowDOMTarget } = props
+ const [devtools] = React.useState(
+ new TanstackQueryDevtoolsPanel({
+ client: queryClient,
+ queryFlavor: 'React Query',
+ version: '5',
+ onlineManager,
+ buttonPosition: 'bottom-left',
+ position: 'bottom',
+ initialIsOpen: true,
+ errorTypes,
+ styleNonce,
+ shadowDOMTarget,
+ onClose: props.onClose,
+ }),
+ )
+
+ React.useEffect(() => {
+ devtools.setClient(queryClient)
+ }, [queryClient, devtools])
+
+ React.useEffect(() => {
+ devtools.setOnClose(props.onClose ?? (() => {}))
+ }, [props.onClose, devtools])
+
+ React.useEffect(() => {
+ devtools.setErrorTypes(errorTypes || [])
+ }, [errorTypes, devtools])
+
+ React.useEffect(() => {
+ if (ref.current) {
+ devtools.mount(ref.current)
+ }
+
+ return () => {
+ devtools.unmount()
+ }
+ }, [devtools])
+
+ return (
+
+ )
+}
diff --git a/packages/react-query-devtools/src/index.ts b/packages/react-query-devtools/src/index.ts
index cdf43cfd0e..e922d8788d 100644
--- a/packages/react-query-devtools/src/index.ts
+++ b/packages/react-query-devtools/src/index.ts
@@ -1,10 +1,18 @@
'use client'
-import * as devtools from './devtools'
+import * as Devtools from './ReactQueryDevtools'
+import * as DevtoolsPanel from './ReactQueryDevtoolsPanel'
-export const ReactQueryDevtools: (typeof devtools)['ReactQueryDevtools'] =
+export const ReactQueryDevtools: (typeof Devtools)['ReactQueryDevtools'] =
process.env.NODE_ENV !== 'development'
? function () {
return null
}
- : devtools.ReactQueryDevtools
+ : Devtools.ReactQueryDevtools
+
+export const ReactQueryDevtoolsPanel: (typeof DevtoolsPanel)['ReactQueryDevtoolsPanel'] =
+ process.env.NODE_ENV !== 'development'
+ ? function () {
+ return null
+ }
+ : DevtoolsPanel.ReactQueryDevtoolsPanel
diff --git a/packages/react-query-devtools/src/production.ts b/packages/react-query-devtools/src/production.ts
index 1abee7eec5..3858485421 100644
--- a/packages/react-query-devtools/src/production.ts
+++ b/packages/react-query-devtools/src/production.ts
@@ -1,5 +1,7 @@
'use client'
-import * as devtools from './devtools'
+import * as Devtools from './ReactQueryDevtools'
+import * as DevtoolsPanel from './ReactQueryDevtoolsPanel'
-export const ReactQueryDevtools = devtools.ReactQueryDevtools
+export const ReactQueryDevtools = Devtools.ReactQueryDevtools
+export const ReactQueryDevtoolsPanel = DevtoolsPanel.ReactQueryDevtoolsPanel
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b4456f905e..63b275a701 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -478,6 +478,31 @@ importers:
specifier: ^5.3.5
version: 5.3.5(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)
+ examples/react/devtools-panel:
+ dependencies:
+ '@tanstack/react-query':
+ specifier: ^5.53.2
+ version: link:../../../packages/react-query
+ '@tanstack/react-query-devtools':
+ specifier: ^5.53.2
+ version: link:../../../packages/react-query-devtools
+ react:
+ specifier: 19.0.0-rc-4c2e457c7c-20240522
+ version: 19.0.0-rc-4c2e457c7c-20240522
+ react-dom:
+ specifier: 19.0.0-rc-4c2e457c7c-20240522
+ version: 19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522)
+ devDependencies:
+ '@vitejs/plugin-react':
+ specifier: ^4.3.1
+ version: 4.3.1(vite@5.3.5(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3))
+ typescript:
+ specifier: 5.3.3
+ version: 5.3.3
+ vite:
+ specifier: ^5.3.5
+ version: 5.3.5(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)
+
examples/react/infinite-query-with-max-pages:
dependencies:
'@tanstack/react-query':
@@ -1100,7 +1125,7 @@ importers:
version: 5.1.0(astro@4.12.3(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.3.3))(tailwindcss@3.4.7)
'@astrojs/vercel':
specifier: ^7.7.2
- version: 7.7.2(astro@4.12.3(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.3.3))(encoding@0.1.13)(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
+ version: 7.7.2(astro@4.12.3(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.3.3))(encoding@0.1.13)(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
'@tanstack/solid-query':
specifier: ^5.53.3
version: link:../../../packages/solid-query
@@ -17490,10 +17515,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@astrojs/vercel@7.7.2(astro@4.12.3(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.3.3))(encoding@0.1.13)(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
+ '@astrojs/vercel@7.7.2(astro@4.12.3(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.3.3))(encoding@0.1.13)(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
dependencies:
'@astrojs/internal-helpers': 0.4.1
- '@vercel/analytics': 1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
+ '@vercel/analytics': 1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
'@vercel/edge': 1.1.2
'@vercel/nft': 0.27.3(encoding@0.1.13)
astro: 4.12.3(@types/node@22.0.2)(less@4.2.0)(sass@1.77.8)(terser@5.31.3)(typescript@5.3.3)
@@ -22753,11 +22778,11 @@ snapshots:
graphql: 15.8.0
wonka: 4.0.15
- '@vercel/analytics@1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
+ '@vercel/analytics@1.3.1(next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
dependencies:
server-only: 0.0.1
optionalDependencies:
- next: 14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522))(react@18.3.1)(sass@1.77.8)
+ next: 14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1
'@vercel/edge@1.1.2': {}
@@ -29867,7 +29892,7 @@ snapshots:
next-tick@1.1.0: {}
- next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522))(react@18.3.1)(sass@1.77.8):
+ next@14.2.5(@babel/core@7.25.2)(react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1))(react@18.3.1)(sass@1.77.8):
dependencies:
'@next/env': 14.2.5
'@swc/helpers': 0.5.5
@@ -29876,7 +29901,7 @@ snapshots:
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.3.1
- react-dom: 19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522)
+ react-dom: 19.0.0-rc-4c2e457c7c-20240522(react@18.3.1)
styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 14.2.5
@@ -32174,6 +32199,12 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
+ react-dom@19.0.0-rc-4c2e457c7c-20240522(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ scheduler: 0.25.0-rc-4c2e457c7c-20240522
+ optional: true
+
react-dom@19.0.0-rc-4c2e457c7c-20240522(react@19.0.0-rc-4c2e457c7c-20240522):
dependencies:
react: 19.0.0-rc-4c2e457c7c-20240522