@tanstack/devtools 0.0.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.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/components/content-panel.cjs +28 -0
  3. package/dist/cjs/components/content-panel.cjs.map +1 -0
  4. package/dist/cjs/components/content-panel.d.cts +6 -0
  5. package/dist/cjs/components/logo.cjs +95 -0
  6. package/dist/cjs/components/logo.cjs.map +1 -0
  7. package/dist/cjs/components/logo.d.cts +1 -0
  8. package/dist/cjs/components/main-panel.cjs +31 -0
  9. package/dist/cjs/components/main-panel.cjs.map +1 -0
  10. package/dist/cjs/components/main-panel.d.cts +7 -0
  11. package/dist/cjs/components/tab-content.cjs +28 -0
  12. package/dist/cjs/components/tab-content.cjs.map +1 -0
  13. package/dist/cjs/components/tab-content.d.cts +2 -0
  14. package/dist/cjs/components/tabs.cjs +47 -0
  15. package/dist/cjs/components/tabs.cjs.map +1 -0
  16. package/dist/cjs/components/tabs.d.cts +5 -0
  17. package/dist/cjs/components/trigger.cjs +31 -0
  18. package/dist/cjs/components/trigger.cjs.map +1 -0
  19. package/dist/cjs/components/trigger.d.cts +5 -0
  20. package/dist/cjs/context/devtools-context.cjs +62 -0
  21. package/dist/cjs/context/devtools-context.cjs.map +1 -0
  22. package/dist/cjs/context/devtools-context.d.cts +20 -0
  23. package/dist/cjs/context/devtools-store.cjs +21 -0
  24. package/dist/cjs/context/devtools-store.cjs.map +1 -0
  25. package/dist/cjs/context/devtools-store.d.cts +53 -0
  26. package/dist/cjs/context/use-devtools-context.cjs +78 -0
  27. package/dist/cjs/context/use-devtools-context.cjs.map +1 -0
  28. package/dist/cjs/context/use-devtools-context.d.cts +36 -0
  29. package/dist/cjs/core.cjs +81 -0
  30. package/dist/cjs/core.cjs.map +1 -0
  31. package/dist/cjs/core.d.cts +13 -0
  32. package/dist/cjs/devtools.cjs +146 -0
  33. package/dist/cjs/devtools.cjs.map +1 -0
  34. package/dist/cjs/devtools.d.cts +1 -0
  35. package/dist/cjs/hooks/use-disable-tabbing.cjs +23 -0
  36. package/dist/cjs/hooks/use-disable-tabbing.cjs.map +1 -0
  37. package/dist/cjs/hooks/use-disable-tabbing.d.cts +6 -0
  38. package/dist/cjs/index.cjs +5 -0
  39. package/dist/cjs/index.cjs.map +1 -0
  40. package/dist/cjs/index.d.cts +3 -0
  41. package/dist/cjs/styles/tokens.cjs +51 -0
  42. package/dist/cjs/styles/tokens.cjs.map +1 -0
  43. package/dist/cjs/styles/tokens.d.cts +298 -0
  44. package/dist/cjs/styles/use-styles.cjs +256 -0
  45. package/dist/cjs/styles/use-styles.cjs.map +1 -0
  46. package/dist/cjs/styles/use-styles.d.cts +21 -0
  47. package/dist/cjs/tabs/index.cjs +19 -0
  48. package/dist/cjs/tabs/index.cjs.map +1 -0
  49. package/dist/cjs/tabs/index.d.cts +12 -0
  50. package/dist/cjs/tabs/plugins-tab.cjs +73 -0
  51. package/dist/cjs/tabs/plugins-tab.cjs.map +1 -0
  52. package/dist/cjs/tabs/plugins-tab.d.cts +1 -0
  53. package/dist/cjs/tabs/settings-tab.cjs +9 -0
  54. package/dist/cjs/tabs/settings-tab.cjs.map +1 -0
  55. package/dist/cjs/tabs/settings-tab.d.cts +1 -0
  56. package/dist/cjs/utils/sanitize.cjs +12 -0
  57. package/dist/cjs/utils/sanitize.cjs.map +1 -0
  58. package/dist/cjs/utils/sanitize.d.cts +1 -0
  59. package/dist/cjs/utils/storage.cjs +19 -0
  60. package/dist/cjs/utils/storage.cjs.map +1 -0
  61. package/dist/cjs/utils/storage.d.cts +5 -0
  62. package/dist/esm/components/content-panel.d.ts +6 -0
  63. package/dist/esm/components/content-panel.js +28 -0
  64. package/dist/esm/components/content-panel.js.map +1 -0
  65. package/dist/esm/components/logo.d.ts +1 -0
  66. package/dist/esm/components/logo.js +95 -0
  67. package/dist/esm/components/logo.js.map +1 -0
  68. package/dist/esm/components/main-panel.d.ts +7 -0
  69. package/dist/esm/components/main-panel.js +31 -0
  70. package/dist/esm/components/main-panel.js.map +1 -0
  71. package/dist/esm/components/tab-content.d.ts +2 -0
  72. package/dist/esm/components/tab-content.js +28 -0
  73. package/dist/esm/components/tab-content.js.map +1 -0
  74. package/dist/esm/components/tabs.d.ts +5 -0
  75. package/dist/esm/components/tabs.js +47 -0
  76. package/dist/esm/components/tabs.js.map +1 -0
  77. package/dist/esm/components/trigger.d.ts +5 -0
  78. package/dist/esm/components/trigger.js +31 -0
  79. package/dist/esm/components/trigger.js.map +1 -0
  80. package/dist/esm/context/devtools-context.d.ts +20 -0
  81. package/dist/esm/context/devtools-context.js +62 -0
  82. package/dist/esm/context/devtools-context.js.map +1 -0
  83. package/dist/esm/context/devtools-store.d.ts +53 -0
  84. package/dist/esm/context/devtools-store.js +21 -0
  85. package/dist/esm/context/devtools-store.js.map +1 -0
  86. package/dist/esm/context/use-devtools-context.d.ts +36 -0
  87. package/dist/esm/context/use-devtools-context.js +78 -0
  88. package/dist/esm/context/use-devtools-context.js.map +1 -0
  89. package/dist/esm/core.d.ts +13 -0
  90. package/dist/esm/core.js +80 -0
  91. package/dist/esm/core.js.map +1 -0
  92. package/dist/esm/devtools.d.ts +1 -0
  93. package/dist/esm/devtools.js +147 -0
  94. package/dist/esm/devtools.js.map +1 -0
  95. package/dist/esm/hooks/use-disable-tabbing.d.ts +6 -0
  96. package/dist/esm/hooks/use-disable-tabbing.js +23 -0
  97. package/dist/esm/hooks/use-disable-tabbing.js.map +1 -0
  98. package/dist/esm/index.d.ts +3 -0
  99. package/dist/esm/index.js +5 -0
  100. package/dist/esm/index.js.map +1 -0
  101. package/dist/esm/styles/tokens.d.ts +298 -0
  102. package/dist/esm/styles/tokens.js +51 -0
  103. package/dist/esm/styles/tokens.js.map +1 -0
  104. package/dist/esm/styles/use-styles.d.ts +21 -0
  105. package/dist/esm/styles/use-styles.js +239 -0
  106. package/dist/esm/styles/use-styles.js.map +1 -0
  107. package/dist/esm/tabs/index.d.ts +12 -0
  108. package/dist/esm/tabs/index.js +19 -0
  109. package/dist/esm/tabs/index.js.map +1 -0
  110. package/dist/esm/tabs/plugins-tab.d.ts +1 -0
  111. package/dist/esm/tabs/plugins-tab.js +73 -0
  112. package/dist/esm/tabs/plugins-tab.js.map +1 -0
  113. package/dist/esm/tabs/settings-tab.d.ts +1 -0
  114. package/dist/esm/tabs/settings-tab.js +9 -0
  115. package/dist/esm/tabs/settings-tab.js.map +1 -0
  116. package/dist/esm/utils/sanitize.d.ts +1 -0
  117. package/dist/esm/utils/sanitize.js +12 -0
  118. package/dist/esm/utils/sanitize.js.map +1 -0
  119. package/dist/esm/utils/storage.d.ts +5 -0
  120. package/dist/esm/utils/storage.js +19 -0
  121. package/dist/esm/utils/storage.js.map +1 -0
  122. package/package.json +69 -0
  123. package/src/components/content-panel.tsx +21 -0
  124. package/src/components/logo.tsx +820 -0
  125. package/src/components/main-panel.tsx +31 -0
  126. package/src/components/tab-content.tsx +20 -0
  127. package/src/components/tabs.tsx +50 -0
  128. package/src/components/trigger.tsx +34 -0
  129. package/src/context/devtools-context.tsx +93 -0
  130. package/src/context/devtools-store.ts +79 -0
  131. package/src/context/use-devtools-context.ts +95 -0
  132. package/src/core.tsx +71 -0
  133. package/src/devtools.tsx +146 -0
  134. package/src/hooks/use-disable-tabbing.ts +28 -0
  135. package/src/index.ts +3 -0
  136. package/src/styles/tokens.ts +305 -0
  137. package/src/styles/use-styles.ts +252 -0
  138. package/src/tabs/index.tsx +62 -0
  139. package/src/tabs/plugins-tab.tsx +47 -0
  140. package/src/tabs/settings-tab.tsx +3 -0
  141. package/src/utils/sanitize.ts +8 -0
  142. package/src/utils/storage.ts +12 -0
@@ -0,0 +1,31 @@
1
+ import clsx from 'clsx'
2
+ import { useHeight } from '../context/use-devtools-context'
3
+ import { useStyles } from '../styles/use-styles'
4
+ import { TANSTACK_DEVTOOLS } from '../utils/storage'
5
+ import type { JSX } from 'solid-js/jsx-runtime'
6
+ import type { Accessor } from 'solid-js'
7
+
8
+ export const MainPanel = (props: {
9
+ isOpen: Accessor<boolean>
10
+ children: JSX.Element
11
+ isResizing: Accessor<boolean>
12
+ }) => {
13
+ const styles = useStyles()
14
+ const { height } = useHeight()
15
+ return (
16
+ <div
17
+ id={TANSTACK_DEVTOOLS}
18
+ style={{
19
+ height: height() + 'px',
20
+ }}
21
+ class={clsx(
22
+ styles().devtoolsPanelContainer,
23
+ styles().devtoolsPanelContainerAnimation(props.isOpen(), height()),
24
+ styles().devtoolsPanelContainerVisibility(props.isOpen()),
25
+ styles().devtoolsPanelContainerResizing(props.isResizing),
26
+ )}
27
+ >
28
+ {props.children}
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,20 @@
1
+ import { createEffect, createSignal } from 'solid-js'
2
+ import { useDevtoolsState } from '../context/use-devtools-context'
3
+ import { tabs } from '../tabs'
4
+ import { useStyles } from '../styles/use-styles'
5
+ import type { JSX } from 'solid-js'
6
+
7
+ export const TabContent = () => {
8
+ const { state } = useDevtoolsState()
9
+ const styles = useStyles()
10
+ const [component, setComponent] = createSignal<JSX.Element | null>(
11
+ tabs.find((t) => t.id === state().activeTab)?.component() || null,
12
+ )
13
+ createEffect(() => {
14
+ setComponent(
15
+ tabs.find((t) => t.id === state().activeTab)?.component() || null,
16
+ )
17
+ })
18
+
19
+ return <div class={styles().tabContent}>{component()}</div>
20
+ }
@@ -0,0 +1,50 @@
1
+ import clsx from 'clsx'
2
+ import { For } from 'solid-js'
3
+ import { useStyles } from '../styles/use-styles'
4
+ import { useDevtoolsState } from '../context/use-devtools-context'
5
+ import { tabs } from '../tabs'
6
+
7
+ interface TabsProps {
8
+ toggleOpen: () => void
9
+ }
10
+
11
+ export const Tabs = (props: TabsProps) => {
12
+ const styles = useStyles()
13
+ const { state, setState } = useDevtoolsState()
14
+
15
+ return (
16
+ <div class={styles().tabContainer}>
17
+ <For each={tabs}>
18
+ {(tab) => (
19
+ <button
20
+ type="button"
21
+ onClick={() => setState({ activeTab: tab.id })}
22
+ class={clsx(styles().tab, { active: state().activeTab === tab.id })}
23
+ >
24
+ {tab.icon}
25
+ </button>
26
+ )}
27
+ </For>
28
+ <button
29
+ type="button"
30
+ class={clsx(styles().tab, 'close')}
31
+ onClick={() => props.toggleOpen()}
32
+ >
33
+ <svg
34
+ xmlns="http://www.w3.org/2000/svg"
35
+ width="24"
36
+ height="24"
37
+ viewBox="0 0 24 24"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ stroke-width="2"
41
+ stroke-linecap="round"
42
+ stroke-linejoin="round"
43
+ >
44
+ <path d="M18 6 6 18" />
45
+ <path d="m6 6 12 12" />
46
+ </svg>
47
+ </button>
48
+ </div>
49
+ )
50
+ }
@@ -0,0 +1,34 @@
1
+ import { createMemo } from 'solid-js'
2
+ import clsx from 'clsx'
3
+ import { useDevtoolsSettings } from '../context/use-devtools-context'
4
+ import { useStyles } from '../styles/use-styles'
5
+ import { TanStackLogo } from './logo'
6
+ import type { Accessor } from 'solid-js'
7
+
8
+ export const Trigger = ({
9
+ isOpen,
10
+ setIsOpen,
11
+ }: {
12
+ isOpen: Accessor<boolean>
13
+ setIsOpen: (isOpen: boolean) => void
14
+ }) => {
15
+ const { settings } = useDevtoolsSettings()
16
+ const styles = useStyles()
17
+ const buttonStyle = createMemo(() => {
18
+ return clsx(
19
+ styles().mainCloseBtn,
20
+ styles().mainCloseBtnPosition(settings().position),
21
+ styles().mainCloseBtnAnimation(isOpen()),
22
+ )
23
+ })
24
+ return (
25
+ <button
26
+ type="button"
27
+ aria-label="Open TanStack Devtools"
28
+ class={buttonStyle()}
29
+ onClick={() => setIsOpen(!isOpen())}
30
+ >
31
+ <TanStackLogo />
32
+ </button>
33
+ )
34
+ }
@@ -0,0 +1,93 @@
1
+ import { createContext } from 'solid-js'
2
+ import { createStore } from 'solid-js/store'
3
+ import { tryParseJson } from '../utils/sanitize'
4
+ import {
5
+ TANSTACK_DEVTOOLS_SETTINGS,
6
+ TANSTACK_DEVTOOLS_STATE,
7
+ getStorageItem,
8
+ setStorageItem,
9
+ } from '../utils/storage'
10
+ import { initialState } from './devtools-store'
11
+ import type { DevtoolsStore } from './devtools-store'
12
+ import type { Setter } from 'solid-js'
13
+ import type { JSX } from 'solid-js/jsx-runtime'
14
+
15
+ export interface DevtoolsPlugin {
16
+ name: string | ((el: HTMLHeadingElement) => void)
17
+ id: string
18
+ render: (el: HTMLDivElement) => void
19
+ }
20
+
21
+ export const DevtoolsContext = createContext<{
22
+ store: DevtoolsStore
23
+ setStore: Setter<DevtoolsStore>
24
+ }>()
25
+
26
+ interface ContextProps {
27
+ children: JSX.Element
28
+ plugins?: Array<DevtoolsPlugin>
29
+ config?: DevtoolsSettings
30
+ }
31
+
32
+ const getSettings = () => {
33
+ const settingsString = getStorageItem(TANSTACK_DEVTOOLS_SETTINGS)
34
+ const settings = tryParseJson<DevtoolsStore['settings']>(settingsString)
35
+ return {
36
+ ...settings,
37
+ }
38
+ }
39
+
40
+ const getExistingStateFromStorage = (
41
+ config?: DevtoolsSettings,
42
+ plugins?: Array<DevtoolsPlugin>,
43
+ ) => {
44
+ const existingState = getStorageItem(TANSTACK_DEVTOOLS_STATE)
45
+ const settings = getSettings()
46
+
47
+ const state: DevtoolsStore = {
48
+ ...initialState,
49
+ plugins: plugins || [],
50
+ state: {
51
+ ...initialState.state,
52
+ ...(existingState ? JSON.parse(existingState) : {}),
53
+ },
54
+ settings: {
55
+ ...initialState.settings,
56
+ ...config,
57
+ ...settings,
58
+ },
59
+ }
60
+ return state
61
+ }
62
+
63
+ export type DevtoolsSettings = DevtoolsStore['settings']
64
+
65
+ export const DevtoolsProvider = (props: ContextProps) => {
66
+ const [store, setStore] = createStore(
67
+ getExistingStateFromStorage(props.config, props.plugins),
68
+ )
69
+
70
+ const value = {
71
+ store,
72
+ setStore: (
73
+ updater: (prev: DevtoolsStore) => DevtoolsStore | Partial<DevtoolsStore>,
74
+ ) => {
75
+ const newState = updater(store)
76
+ const { settings, state: internalState } = newState
77
+ // Store user settings for dev tools into local storage
78
+ setStorageItem(TANSTACK_DEVTOOLS_SETTINGS, JSON.stringify(settings))
79
+ // Store general state into local storage
80
+ setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(internalState))
81
+ setStore((prev) => ({
82
+ ...prev,
83
+ ...newState,
84
+ }))
85
+ },
86
+ }
87
+
88
+ return (
89
+ <DevtoolsContext.Provider value={value}>
90
+ {props.children}
91
+ </DevtoolsContext.Provider>
92
+ )
93
+ }
@@ -0,0 +1,79 @@
1
+ import type { TabName } from '../tabs'
2
+ import type { DevtoolsPlugin } from './devtools-context'
3
+
4
+ type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift'
5
+ type KeyboardKey = ModifierKey | (string & {})
6
+
7
+ type TriggerPosition =
8
+ | 'top-left'
9
+ | 'top-right'
10
+ | 'bottom-left'
11
+ | 'bottom-right'
12
+ | 'middle-left'
13
+ | 'middle-right'
14
+
15
+ export type DevtoolsStore = {
16
+ settings: {
17
+ /**
18
+ * Whether the dev tools should be open by default
19
+ * @default false
20
+ */
21
+ defaultOpen: boolean
22
+ /**
23
+ * Whether the dev tools trigger should be hidden until the user hovers over it
24
+ * @default false
25
+ */
26
+ hideUntilHover: boolean
27
+ /**
28
+ * The position of the trigger button
29
+ * @default "bottom-right"
30
+ */
31
+ position: TriggerPosition
32
+
33
+ /**
34
+ * The location of the panel once it is open
35
+ * @default "bottom"
36
+ */
37
+ panelLocation: 'top' | 'bottom'
38
+ /**
39
+ * The hotkey to open the dev tools
40
+ * @default "shift+a"
41
+ */
42
+ openHotkey: Array<KeyboardKey>
43
+ /**
44
+ * Whether to require the URL flag to open the dev tools
45
+ * @default false
46
+ */
47
+ requireUrlFlag: boolean
48
+ /**
49
+ * The URL flag to open the dev tools, used in conjunction with requireUrlFlag (if set to true)
50
+ * @default "tanstack-devtools"
51
+ */
52
+ urlFlag: string
53
+ }
54
+ state: {
55
+ activeTab: TabName
56
+ height: number
57
+ activePlugin?: DevtoolsPlugin | undefined
58
+ persistOpen: boolean
59
+ }
60
+ plugins?: Array<DevtoolsPlugin>
61
+ }
62
+
63
+ export const initialState: DevtoolsStore = {
64
+ settings: {
65
+ defaultOpen: false,
66
+ hideUntilHover: false,
67
+ position: 'bottom-right',
68
+ panelLocation: 'bottom',
69
+ openHotkey: ['Shift', 'A'],
70
+ requireUrlFlag: false,
71
+ urlFlag: 'tanstack-devtools',
72
+ },
73
+ state: {
74
+ activeTab: 'plugins',
75
+ height: 400,
76
+ activePlugin: undefined,
77
+ persistOpen: false,
78
+ },
79
+ }
@@ -0,0 +1,95 @@
1
+ import { createMemo, useContext } from 'solid-js'
2
+ import { DevtoolsContext } from './devtools-context.jsx'
3
+ import type { DevtoolsPlugin } from './devtools-context.jsx'
4
+ /* import type { DevtoolsPlugin } from './devtools-context' */
5
+ import type { DevtoolsStore } from './devtools-store.js'
6
+
7
+ /**
8
+ * Returns an object containing the current state and setState function of the ShellContext.
9
+ * Throws an error if used outside of a ShellContextProvider.
10
+ */
11
+ const useDevtoolsContext = () => {
12
+ const context = useContext(DevtoolsContext)
13
+ if (context === undefined) {
14
+ throw new Error(
15
+ 'useDevtoolsShellContext must be used within a ShellContextProvider',
16
+ )
17
+ }
18
+ return context
19
+ }
20
+
21
+ export const usePlugins = () => {
22
+ const { store, setStore } = useDevtoolsContext()
23
+
24
+ const plugins = createMemo(() => store.plugins)
25
+ const activePlugin = createMemo(() => store.state.activePlugin)
26
+
27
+ const setActivePlugin = (plugin: DevtoolsPlugin) => {
28
+ setStore((prev) => ({
29
+ ...prev,
30
+ state: {
31
+ ...prev.state,
32
+ activePlugin: plugin,
33
+ },
34
+ }))
35
+ }
36
+
37
+ return { plugins, setActivePlugin, activePlugin }
38
+ }
39
+
40
+ export const useDevtoolsState = () => {
41
+ const { store, setStore } = useDevtoolsContext()
42
+ const state = createMemo(() => store.state)
43
+ const setState = (newState: Partial<DevtoolsStore['state']>) => {
44
+ setStore((prev) => ({
45
+ ...prev,
46
+ state: {
47
+ ...prev.state,
48
+ ...newState,
49
+ },
50
+ }))
51
+ }
52
+ return { state, setState }
53
+ }
54
+
55
+ export const useDevtoolsSettings = () => {
56
+ const { store, setStore } = useDevtoolsContext()
57
+
58
+ const settings = createMemo(() => store.settings)
59
+
60
+ const setSettings = (newSettings: Partial<DevtoolsStore['settings']>) => {
61
+ setStore((prev) => ({
62
+ ...prev,
63
+ settings: {
64
+ ...prev.settings,
65
+ ...newSettings,
66
+ },
67
+ }))
68
+ }
69
+
70
+ return { setSettings, settings }
71
+ }
72
+
73
+ export const usePersistOpen = () => {
74
+ const { state, setState } = useDevtoolsState()
75
+
76
+ const persistOpen = createMemo(() => state().persistOpen)
77
+
78
+ const setPersistOpen = (value: boolean) => {
79
+ setState({ persistOpen: value })
80
+ }
81
+
82
+ return { persistOpen, setPersistOpen }
83
+ }
84
+
85
+ export const useHeight = () => {
86
+ const { state, setState } = useDevtoolsState()
87
+
88
+ const height = createMemo(() => state().height)
89
+
90
+ const setHeight = (newHeight: number) => {
91
+ setState({ height: newHeight })
92
+ }
93
+
94
+ return { height, setHeight }
95
+ }
package/src/core.tsx ADDED
@@ -0,0 +1,71 @@
1
+ import { lazy } from 'solid-js'
2
+ import { Portal, render } from 'solid-js/web'
3
+ import { DevtoolsProvider } from './context/devtools-context'
4
+ import { initialState } from './context/devtools-store'
5
+ import type {
6
+ DevtoolsPlugin,
7
+ DevtoolsSettings,
8
+ } from './context/devtools-context'
9
+
10
+ export interface DevtoolsOptions {
11
+ options?: Partial<DevtoolsSettings>
12
+ plugins?: Array<DevtoolsPlugin>
13
+ }
14
+
15
+ class TanStackDevtoolsCore {
16
+ #options: DevtoolsSettings = {
17
+ ...initialState.settings,
18
+ }
19
+ #plugins: Array<DevtoolsPlugin> = []
20
+ #isMounted = false
21
+ #dispose?: () => void
22
+ #Component: any
23
+
24
+ constructor(config: DevtoolsOptions) {
25
+ this.#plugins = config.plugins || []
26
+ this.#options = {
27
+ ...this.#options,
28
+ ...config.options,
29
+ }
30
+ }
31
+
32
+ mount<T extends HTMLElement>(el: T) {
33
+ if (this.#isMounted) {
34
+ throw new Error('Devtools is already mounted')
35
+ }
36
+ const mountTo = el
37
+ const dispose = render(() => {
38
+ this.#Component = lazy(() => import('./devtools'))
39
+
40
+ const Devtools = this.#Component
41
+
42
+ return (
43
+ <DevtoolsProvider plugins={this.#plugins} config={this.#options}>
44
+ <Portal mount={mountTo}>
45
+ <Devtools />
46
+ </Portal>
47
+ </DevtoolsProvider>
48
+ )
49
+ }, mountTo)
50
+
51
+ this.#isMounted = true
52
+ this.#dispose = dispose
53
+ }
54
+
55
+ unmount() {
56
+ if (!this.#isMounted) {
57
+ throw new Error('Devtools is not mounted')
58
+ }
59
+ this.#dispose?.()
60
+ this.#isMounted = false
61
+ }
62
+
63
+ setOptions(options: Partial<DevtoolsOptions>) {
64
+ this.#options = {
65
+ ...this.#options,
66
+ ...options,
67
+ }
68
+ }
69
+ }
70
+
71
+ export { TanStackDevtoolsCore as TanStackRouterDevtoolsCore }
@@ -0,0 +1,146 @@
1
+ import { createEffect, createSignal } from 'solid-js'
2
+ import { createShortcut } from '@solid-primitives/keyboard'
3
+ import {
4
+ useDevtoolsSettings,
5
+ useHeight,
6
+ usePersistOpen,
7
+ } from './context/use-devtools-context'
8
+ import { useDisableTabbing } from './hooks/use-disable-tabbing'
9
+ import { TANSTACK_DEVTOOLS } from './utils/storage'
10
+ import { Trigger } from './components/trigger'
11
+ import { MainPanel } from './components/main-panel'
12
+ import { ContentPanel } from './components/content-panel'
13
+ import { Tabs } from './components/tabs'
14
+ import { TabContent } from './components/tab-content'
15
+
16
+ export default function DevTools() {
17
+ const { settings } = useDevtoolsSettings()
18
+ const { setHeight } = useHeight()
19
+ const { persistOpen, setPersistOpen } = usePersistOpen()
20
+ const [rootEl, setRootEl] = createSignal<HTMLDivElement>()
21
+ const [isOpen, setIsOpen] = createSignal(
22
+ settings().defaultOpen || persistOpen(),
23
+ )
24
+ let panelRef: HTMLDivElement | undefined = undefined
25
+ const [isResizing, setIsResizing] = createSignal(false)
26
+ const toggleOpen = () => {
27
+ const open = isOpen()
28
+ setIsOpen(!open)
29
+ setPersistOpen(!open)
30
+ }
31
+
32
+ // Used to resize the panel
33
+ const handleDragStart = (
34
+ panelElement: HTMLDivElement | undefined,
35
+ startEvent: MouseEvent,
36
+ ) => {
37
+ if (startEvent.button !== 0) return // Only allow left click for drag
38
+
39
+ setIsResizing(true)
40
+
41
+ const dragInfo = {
42
+ originalHeight: panelElement?.getBoundingClientRect().height ?? 0,
43
+ pageY: startEvent.pageY,
44
+ }
45
+
46
+ const run = (moveEvent: MouseEvent) => {
47
+ const delta = dragInfo.pageY - moveEvent.pageY
48
+ const newHeight = dragInfo.originalHeight + delta
49
+
50
+ setHeight(newHeight)
51
+
52
+ if (newHeight < 70) {
53
+ setIsOpen(false)
54
+ } else {
55
+ setIsOpen(true)
56
+ }
57
+ }
58
+
59
+ const unsub = () => {
60
+ setIsResizing(false)
61
+ document.removeEventListener('mousemove', run)
62
+ document.removeEventListener('mouseUp', unsub)
63
+ }
64
+
65
+ document.addEventListener('mousemove', run)
66
+ document.addEventListener('mouseup', unsub)
67
+ }
68
+
69
+ // Handle resizing and padding adjustments
70
+ createEffect(() => {
71
+ if (isOpen()) {
72
+ const previousValue = rootEl()?.parentElement?.style.paddingBottom
73
+
74
+ const run = () => {
75
+ const containerHeight = panelRef!.getBoundingClientRect().height
76
+ if (rootEl()?.parentElement) {
77
+ setRootEl((prev) => {
78
+ if (prev?.parentElement) {
79
+ prev.parentElement.style.paddingBottom = `${containerHeight}px`
80
+ }
81
+ return prev
82
+ })
83
+ }
84
+ }
85
+
86
+ run()
87
+
88
+ if (typeof window !== 'undefined') {
89
+ window.addEventListener('resize', run)
90
+
91
+ return () => {
92
+ window.removeEventListener('resize', run)
93
+ if (rootEl()?.parentElement && typeof previousValue === 'string') {
94
+ setRootEl((prev) => {
95
+ prev!.parentElement!.style.paddingBottom = previousValue
96
+ return prev
97
+ })
98
+ }
99
+ }
100
+ }
101
+ } else {
102
+ // Reset padding when devtools are closed
103
+ if (rootEl()?.parentElement) {
104
+ setRootEl((prev) => {
105
+ if (prev?.parentElement) {
106
+ prev.parentElement.removeAttribute('style')
107
+ }
108
+ return prev
109
+ })
110
+ }
111
+ }
112
+ return
113
+ })
114
+ createEffect(() => {
115
+ window.addEventListener('keydown', (e) => {
116
+ if (e.key === 'Escape' && isOpen()) {
117
+ toggleOpen()
118
+ }
119
+ })
120
+ })
121
+ useDisableTabbing(isOpen)
122
+ createEffect(() => {
123
+ if (rootEl()) {
124
+ const el = rootEl()
125
+ const fontSize = getComputedStyle(el!).fontSize
126
+ el?.style.setProperty('--tsrd-font-size', fontSize)
127
+ }
128
+ })
129
+ createShortcut(settings().openHotkey, () => {
130
+ toggleOpen()
131
+ })
132
+ return (
133
+ <div ref={setRootEl} data-testid={TANSTACK_DEVTOOLS}>
134
+ <Trigger isOpen={isOpen} setIsOpen={toggleOpen} />
135
+ <MainPanel isResizing={isResizing} isOpen={isOpen}>
136
+ <ContentPanel
137
+ ref={(ref) => (panelRef = ref)}
138
+ handleDragStart={(e) => handleDragStart(panelRef, e)}
139
+ >
140
+ <Tabs toggleOpen={toggleOpen} />
141
+ <TabContent />
142
+ </ContentPanel>
143
+ </MainPanel>
144
+ </div>
145
+ )
146
+ }
@@ -0,0 +1,28 @@
1
+ import { createEffect } from 'solid-js'
2
+ import { TANSTACK_DEVTOOLS } from '../utils/storage'
3
+
4
+ const recursivelyChangeTabIndex = (
5
+ node: Element | HTMLElement,
6
+ remove = true,
7
+ ) => {
8
+ if (remove) {
9
+ node.setAttribute('tabIndex', '-1')
10
+ } else {
11
+ node.removeAttribute('tabIndex')
12
+ }
13
+ for (const child of node.children) {
14
+ recursivelyChangeTabIndex(child, remove)
15
+ }
16
+ }
17
+ /**
18
+ * @param isOpen A function that returns whether the devtools are open or not.
19
+ * This is used to disable tabbing over the main devtools container while
20
+ * the devtools are closed.
21
+ */
22
+ export const useDisableTabbing = (isOpen: () => boolean) => {
23
+ createEffect(() => {
24
+ const el = document.getElementById(TANSTACK_DEVTOOLS)
25
+ if (!el) return
26
+ recursivelyChangeTabIndex(el, !isOpen())
27
+ })
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { TanStackRouterDevtoolsCore } from './core'
2
+ export type { DevtoolsOptions } from './core'
3
+ export type { DevtoolsPlugin } from './context/devtools-context'