@tanstack/router-devtools-core 1.114.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/cjs/AgeTicker.cjs +47 -0
  4. package/dist/cjs/AgeTicker.cjs.map +1 -0
  5. package/dist/cjs/AgeTicker.d.cts +6 -0
  6. package/dist/cjs/BaseTanStackRouterDevtoolsPanel.cjs +505 -0
  7. package/dist/cjs/BaseTanStackRouterDevtoolsPanel.cjs.map +1 -0
  8. package/dist/cjs/BaseTanStackRouterDevtoolsPanel.d.cts +34 -0
  9. package/dist/cjs/Explorer.cjs +307 -0
  10. package/dist/cjs/Explorer.cjs.map +1 -0
  11. package/dist/cjs/Explorer.d.cts +43 -0
  12. package/dist/cjs/FloatingTanStackRouterDevtools.cjs +195 -0
  13. package/dist/cjs/FloatingTanStackRouterDevtools.cjs.map +1 -0
  14. package/dist/cjs/FloatingTanStackRouterDevtools.d.cts +48 -0
  15. package/dist/cjs/TanStackRouterDevtoolsCore.cjs +99 -0
  16. package/dist/cjs/TanStackRouterDevtoolsCore.cjs.map +1 -0
  17. package/dist/cjs/TanStackRouterDevtoolsCore.d.cts +55 -0
  18. package/dist/cjs/TanStackRouterDevtoolsPanelCore.cjs +99 -0
  19. package/dist/cjs/TanStackRouterDevtoolsPanelCore.cjs.map +1 -0
  20. package/dist/cjs/TanStackRouterDevtoolsPanelCore.d.cts +43 -0
  21. package/dist/cjs/context.cjs +20 -0
  22. package/dist/cjs/context.cjs.map +1 -0
  23. package/dist/cjs/context.d.cts +13 -0
  24. package/dist/cjs/index.cjs +7 -0
  25. package/dist/cjs/index.cjs.map +1 -0
  26. package/dist/cjs/index.d.cts +2 -0
  27. package/dist/cjs/logo.cjs +92 -0
  28. package/dist/cjs/logo.cjs.map +1 -0
  29. package/dist/cjs/logo.d.cts +1 -0
  30. package/dist/cjs/theme.d.cts +34 -0
  31. package/dist/cjs/tokens.cjs +201 -0
  32. package/dist/cjs/tokens.cjs.map +1 -0
  33. package/dist/cjs/tokens.d.cts +298 -0
  34. package/dist/cjs/useLocalStorage.cjs +42 -0
  35. package/dist/cjs/useLocalStorage.cjs.map +1 -0
  36. package/dist/cjs/useLocalStorage.d.cts +2 -0
  37. package/dist/cjs/useMediaQuery.d.cts +2 -0
  38. package/dist/cjs/useStyles.cjs +582 -0
  39. package/dist/cjs/useStyles.cjs.map +1 -0
  40. package/dist/cjs/useStyles.d.cts +53 -0
  41. package/dist/cjs/utils.cjs +63 -0
  42. package/dist/cjs/utils.cjs.map +1 -0
  43. package/dist/cjs/utils.d.cts +25 -0
  44. package/dist/esm/AgeTicker.d.ts +6 -0
  45. package/dist/esm/AgeTicker.js +47 -0
  46. package/dist/esm/AgeTicker.js.map +1 -0
  47. package/dist/esm/BaseTanStackRouterDevtoolsPanel.d.ts +34 -0
  48. package/dist/esm/BaseTanStackRouterDevtoolsPanel.js +505 -0
  49. package/dist/esm/BaseTanStackRouterDevtoolsPanel.js.map +1 -0
  50. package/dist/esm/Explorer.d.ts +43 -0
  51. package/dist/esm/Explorer.js +290 -0
  52. package/dist/esm/Explorer.js.map +1 -0
  53. package/dist/esm/FloatingTanStackRouterDevtools.d.ts +48 -0
  54. package/dist/esm/FloatingTanStackRouterDevtools.js +195 -0
  55. package/dist/esm/FloatingTanStackRouterDevtools.js.map +1 -0
  56. package/dist/esm/TanStackRouterDevtoolsCore.d.ts +55 -0
  57. package/dist/esm/TanStackRouterDevtoolsCore.js +99 -0
  58. package/dist/esm/TanStackRouterDevtoolsCore.js.map +1 -0
  59. package/dist/esm/TanStackRouterDevtoolsPanelCore.d.ts +43 -0
  60. package/dist/esm/TanStackRouterDevtoolsPanelCore.js +99 -0
  61. package/dist/esm/TanStackRouterDevtoolsPanelCore.js.map +1 -0
  62. package/dist/esm/context.d.ts +13 -0
  63. package/dist/esm/context.js +20 -0
  64. package/dist/esm/context.js.map +1 -0
  65. package/dist/esm/index.d.ts +2 -0
  66. package/dist/esm/index.js +7 -0
  67. package/dist/esm/index.js.map +1 -0
  68. package/dist/esm/logo.d.ts +1 -0
  69. package/dist/esm/logo.js +92 -0
  70. package/dist/esm/logo.js.map +1 -0
  71. package/dist/esm/theme.d.ts +34 -0
  72. package/dist/esm/tokens.d.ts +298 -0
  73. package/dist/esm/tokens.js +201 -0
  74. package/dist/esm/tokens.js.map +1 -0
  75. package/dist/esm/useLocalStorage.d.ts +2 -0
  76. package/dist/esm/useLocalStorage.js +43 -0
  77. package/dist/esm/useLocalStorage.js.map +1 -0
  78. package/dist/esm/useMediaQuery.d.ts +2 -0
  79. package/dist/esm/useStyles.d.ts +53 -0
  80. package/dist/esm/useStyles.js +565 -0
  81. package/dist/esm/useStyles.js.map +1 -0
  82. package/dist/esm/utils.d.ts +25 -0
  83. package/dist/esm/utils.js +63 -0
  84. package/dist/esm/utils.js.map +1 -0
  85. package/package.json +71 -0
  86. package/src/AgeTicker.tsx +59 -0
  87. package/src/BaseTanStackRouterDevtoolsPanel.tsx +559 -0
  88. package/src/Explorer.tsx +339 -0
  89. package/src/FloatingTanStackRouterDevtools.tsx +280 -0
  90. package/src/TanStackRouterDevtoolsCore.tsx +139 -0
  91. package/src/TanStackRouterDevtoolsPanelCore.tsx +120 -0
  92. package/src/context.ts +24 -0
  93. package/src/index.tsx +2 -0
  94. package/src/logo.tsx +817 -0
  95. package/src/theme.tsx +36 -0
  96. package/src/tokens.ts +305 -0
  97. package/src/useLocalStorage.ts +52 -0
  98. package/src/useMediaQuery.ts +44 -0
  99. package/src/useStyles.tsx +589 -0
  100. package/src/utils.tsx +185 -0
@@ -0,0 +1,339 @@
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
+ import { clsx as cx } from 'clsx'
3
+ import * as goober from 'goober'
4
+ import { createMemo, createSignal, useContext } from 'solid-js'
5
+ import { tokens } from './tokens'
6
+ import { displayValue } from './utils'
7
+ import { ShadowDomTargetContext } from './context'
8
+ import type { Accessor, JSX } from 'solid-js'
9
+
10
+ type ExpanderProps = {
11
+ expanded: boolean
12
+ style?: JSX.CSSProperties
13
+ }
14
+
15
+ export const Expander = ({ expanded, style = {} }: ExpanderProps) => {
16
+ const styles = useStyles()
17
+ return (
18
+ <span class={styles().expander}>
19
+ <svg
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ width="12"
22
+ height="12"
23
+ fill="none"
24
+ viewBox="0 0 24 24"
25
+ class={cx(styles().expanderIcon(expanded))}
26
+ >
27
+ <path
28
+ stroke="currentColor"
29
+ stroke-linecap="round"
30
+ stroke-linejoin="round"
31
+ stroke-width="2"
32
+ d="M9 18l6-6-6-6"
33
+ ></path>
34
+ </svg>
35
+ </span>
36
+ )
37
+ }
38
+
39
+ type Entry = {
40
+ label: string
41
+ }
42
+
43
+ type RendererProps = {
44
+ handleEntry: HandleEntryFn
45
+ label?: JSX.Element
46
+ value: Accessor<unknown>
47
+ subEntries: Array<Entry>
48
+ subEntryPages: Array<Array<Entry>>
49
+ type: string
50
+ expanded: Accessor<boolean>
51
+ toggleExpanded: () => void
52
+ pageSize: number
53
+ filterSubEntries?: (subEntries: Array<Property>) => Array<Property>
54
+ }
55
+
56
+ /**
57
+ * Chunk elements in the array by size
58
+ *
59
+ * when the array cannot be chunked evenly by size, the last chunk will be
60
+ * filled with the remaining elements
61
+ *
62
+ * @example
63
+ * chunkArray(['a','b', 'c', 'd', 'e'], 2) // returns [['a','b'], ['c', 'd'], ['e']]
64
+ */
65
+ export function chunkArray<T>(array: Array<T>, size: number): Array<Array<T>> {
66
+ if (size < 1) return []
67
+ let i = 0
68
+ const result: Array<Array<T>> = []
69
+ while (i < array.length) {
70
+ result.push(array.slice(i, i + size))
71
+ i = i + size
72
+ }
73
+ return result
74
+ }
75
+
76
+ type HandleEntryFn = (entry: Entry) => JSX.Element
77
+
78
+ type ExplorerProps = Partial<RendererProps> & {
79
+ defaultExpanded?: true | Record<string, boolean>
80
+ value: Accessor<unknown>
81
+ }
82
+
83
+ type Property = {
84
+ defaultExpanded?: boolean | Record<string, boolean>
85
+ label: string
86
+ value: unknown
87
+ }
88
+
89
+ function isIterable(x: any): x is Iterable<unknown> {
90
+ return Symbol.iterator in x
91
+ }
92
+
93
+ export function Explorer({
94
+ value,
95
+ defaultExpanded,
96
+ pageSize = 100,
97
+ filterSubEntries,
98
+ ...rest
99
+ }: ExplorerProps) {
100
+ const [expanded, setExpanded] = createSignal(Boolean(defaultExpanded))
101
+ const toggleExpanded = () => setExpanded((old) => !old)
102
+
103
+ const type = createMemo(() => typeof value())
104
+ const subEntries = createMemo(() => {
105
+ let entries: Array<Property> = []
106
+
107
+ const makeProperty = (sub: { label: string; value: unknown }): Property => {
108
+ const subDefaultExpanded =
109
+ defaultExpanded === true
110
+ ? { [sub.label]: true }
111
+ : defaultExpanded?.[sub.label]
112
+ return {
113
+ ...sub,
114
+ value: () => sub.value,
115
+ defaultExpanded: subDefaultExpanded,
116
+ }
117
+ }
118
+
119
+ if (Array.isArray(value())) {
120
+ // any[]
121
+ entries = (value() as Array<any>).map((d, i) =>
122
+ makeProperty({
123
+ label: i.toString(),
124
+ value: d,
125
+ }),
126
+ )
127
+ } else if (
128
+ value() !== null &&
129
+ typeof value() === 'object' &&
130
+ isIterable(value()) &&
131
+ typeof (value() as Iterable<unknown>)[Symbol.iterator] === 'function'
132
+ ) {
133
+ // Iterable<unknown>
134
+ entries = Array.from(value() as Iterable<unknown>, (val, i) =>
135
+ makeProperty({
136
+ label: i.toString(),
137
+ value: val,
138
+ }),
139
+ )
140
+ } else if (typeof value() === 'object' && value() !== null) {
141
+ // object
142
+ entries = Object.entries(value() as object).map(([key, val]) =>
143
+ makeProperty({
144
+ label: key,
145
+ value: val,
146
+ }),
147
+ )
148
+ }
149
+
150
+ return filterSubEntries ? filterSubEntries(entries) : entries
151
+ })
152
+
153
+ const subEntryPages = createMemo(() => chunkArray(subEntries(), pageSize))
154
+
155
+ const [expandedPages, setExpandedPages] = createSignal<Array<number>>([])
156
+ const [valueSnapshot, setValueSnapshot] = createSignal(undefined)
157
+ const styles = useStyles()
158
+
159
+ const refreshValueSnapshot = () => {
160
+ setValueSnapshot((value() as () => any)())
161
+ }
162
+
163
+ const handleEntry = (entry: Entry) => (
164
+ <Explorer
165
+ value={value}
166
+ filterSubEntries={filterSubEntries}
167
+ {...rest}
168
+ {...entry}
169
+ />
170
+ )
171
+
172
+ return (
173
+ <div class={styles().entry}>
174
+ {subEntryPages().length ? (
175
+ <>
176
+ <button
177
+ class={styles().expandButton}
178
+ onClick={() => toggleExpanded()}
179
+ >
180
+ <Expander expanded={expanded() ?? false} />
181
+ {rest.label}
182
+ <span class={styles().info}>
183
+ {String(type).toLowerCase() === 'iterable' ? '(Iterable) ' : ''}
184
+ {subEntries().length} {subEntries().length > 1 ? `items` : `item`}
185
+ </span>
186
+ </button>
187
+ {(expanded() ?? false) ? (
188
+ subEntryPages().length === 1 ? (
189
+ <div class={styles().subEntries}>
190
+ {subEntries().map((entry, index) => handleEntry(entry))}
191
+ </div>
192
+ ) : (
193
+ <div class={styles().subEntries}>
194
+ {subEntryPages().map((entries, index) => {
195
+ return (
196
+ <div>
197
+ <div class={styles().entry}>
198
+ <button
199
+ class={cx(styles().labelButton, 'labelButton')}
200
+ onClick={() =>
201
+ setExpandedPages((old) =>
202
+ old.includes(index)
203
+ ? old.filter((d) => d !== index)
204
+ : [...old, index],
205
+ )
206
+ }
207
+ >
208
+ <Expander
209
+ expanded={expandedPages().includes(index)}
210
+ />{' '}
211
+ [{index * pageSize} ...{' '}
212
+ {index * pageSize + pageSize - 1}]
213
+ </button>
214
+ {expandedPages().includes(index) ? (
215
+ <div class={styles().subEntries}>
216
+ {entries.map((entry) => handleEntry(entry))}
217
+ </div>
218
+ ) : null}
219
+ </div>
220
+ </div>
221
+ )
222
+ })}
223
+ </div>
224
+ )
225
+ ) : null}
226
+ </>
227
+ ) : type() === 'function' ? (
228
+ <>
229
+ <Explorer
230
+ label={
231
+ <button
232
+ onClick={refreshValueSnapshot}
233
+ class={styles().refreshValueBtn}
234
+ >
235
+ <span>{rest.label}</span> 🔄{' '}
236
+ </button>
237
+ }
238
+ value={valueSnapshot}
239
+ defaultExpanded={{}}
240
+ />
241
+ </>
242
+ ) : (
243
+ <>
244
+ <span>{rest.label}:</span>{' '}
245
+ <span class={styles().value}>{displayValue(value())}</span>
246
+ </>
247
+ )}
248
+ </div>
249
+ )
250
+ }
251
+
252
+ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
253
+ const { colors, font, size, alpha, shadow, border } = tokens
254
+ const { fontFamily, lineHeight, size: fontSize } = font
255
+ const css = shadowDOMTarget
256
+ ? goober.css.bind({ target: shadowDOMTarget })
257
+ : goober.css
258
+
259
+ return {
260
+ entry: css`
261
+ font-family: ${fontFamily.mono};
262
+ font-size: ${fontSize.xs};
263
+ line-height: ${lineHeight.sm};
264
+ outline: none;
265
+ word-break: break-word;
266
+ `,
267
+ labelButton: css`
268
+ cursor: pointer;
269
+ color: inherit;
270
+ font: inherit;
271
+ outline: inherit;
272
+ background: transparent;
273
+ border: none;
274
+ padding: 0;
275
+ `,
276
+ expander: css`
277
+ display: inline-flex;
278
+ align-items: center;
279
+ justify-content: center;
280
+ width: ${size[3]};
281
+ height: ${size[3]};
282
+ padding-left: 3px;
283
+ box-sizing: content-box;
284
+ `,
285
+ expanderIcon: (expanded: boolean) => {
286
+ if (expanded) {
287
+ return css`
288
+ transform: rotate(90deg);
289
+ transition: transform 0.1s ease;
290
+ `
291
+ }
292
+ return css`
293
+ transform: rotate(0deg);
294
+ transition: transform 0.1s ease;
295
+ `
296
+ },
297
+ expandButton: css`
298
+ display: flex;
299
+ gap: ${size[1]};
300
+ align-items: center;
301
+ cursor: pointer;
302
+ color: inherit;
303
+ font: inherit;
304
+ outline: inherit;
305
+ background: transparent;
306
+ border: none;
307
+ padding: 0;
308
+ `,
309
+ value: css`
310
+ color: ${colors.purple[400]};
311
+ `,
312
+ subEntries: css`
313
+ margin-left: ${size[2]};
314
+ padding-left: ${size[2]};
315
+ border-left: 2px solid ${colors.darkGray[400]};
316
+ `,
317
+ info: css`
318
+ color: ${colors.gray[500]};
319
+ font-size: ${fontSize['2xs']};
320
+ padding-left: ${size[1]};
321
+ `,
322
+ refreshValueBtn: css`
323
+ appearance: none;
324
+ border: 0;
325
+ cursor: pointer;
326
+ background: transparent;
327
+ color: inherit;
328
+ padding: 0;
329
+ font-family: ${fontFamily.mono};
330
+ font-size: ${fontSize.xs};
331
+ `,
332
+ }
333
+ }
334
+
335
+ function useStyles() {
336
+ const shadowDomTarget = useContext(ShadowDomTargetContext)
337
+ const [_styles] = createSignal(stylesFactory(shadowDomTarget))
338
+ return _styles
339
+ }
@@ -0,0 +1,280 @@
1
+ import { clsx as cx } from 'clsx'
2
+
3
+ import { createEffect, createMemo, createSignal } from 'solid-js'
4
+ import { Dynamic } from 'solid-js/web'
5
+
6
+ import { DevtoolsOnCloseContext } from './context'
7
+ import { useIsMounted } from './utils'
8
+ import { BaseTanStackRouterDevtoolsPanel } from './BaseTanStackRouterDevtoolsPanel'
9
+ import useLocalStorage from './useLocalStorage'
10
+ import { TanStackLogo } from './logo'
11
+ import { useStyles } from './useStyles'
12
+ import type { Accessor, JSX } from 'solid-js'
13
+ import type { AnyRouter } from '@tanstack/router-core'
14
+
15
+ export interface FloatingDevtoolsOptions {
16
+ /**
17
+ * Set this true if you want the dev tools to default to being open
18
+ */
19
+ initialIsOpen?: boolean
20
+ /**
21
+ * Use this to add props to the panel. For example, you can add class, style (merge and override default style), etc.
22
+ */
23
+ panelProps?: any & {
24
+ ref?: any
25
+ }
26
+ /**
27
+ * Use this to add props to the close button. For example, you can add class, style (merge and override default style), onClick (extend default handler), etc.
28
+ */
29
+ closeButtonProps?: any & {
30
+ ref?: any
31
+ }
32
+ /**
33
+ * Use this to add props to the toggle button. For example, you can add class, style (merge and override default style), onClick (extend default handler), etc.
34
+ */
35
+ toggleButtonProps?: any & {
36
+ ref?: any
37
+ }
38
+ /**
39
+ * The position of the TanStack Router logo to open and close the devtools panel.
40
+ * Defaults to 'bottom-left'.
41
+ */
42
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
43
+ /**
44
+ * Use this to render the devtools inside a different type of container element for a11y purposes.
45
+ * Any string which corresponds to a valid intrinsic JSX element is allowed.
46
+ * Defaults to 'footer'.
47
+ */
48
+ containerElement?: string | any
49
+ /**
50
+ * A boolean variable indicating if the "lite" version of the library is being used
51
+ */
52
+ router: Accessor<AnyRouter>
53
+ routerState: Accessor<any>
54
+ /**
55
+ * Use this to attach the devtool's styles to specific element in the DOM.
56
+ */
57
+ shadowDOMTarget?: ShadowRoot
58
+ }
59
+
60
+ export function FloatingTanStackRouterDevtools({
61
+ initialIsOpen,
62
+ panelProps = {},
63
+ closeButtonProps = {},
64
+ toggleButtonProps = {},
65
+ position = 'bottom-left',
66
+ containerElement: Container = 'footer',
67
+ router,
68
+ routerState,
69
+ shadowDOMTarget,
70
+ }: FloatingDevtoolsOptions): JSX.Element | null {
71
+ const [rootEl, setRootEl] = createSignal<HTMLDivElement>()
72
+
73
+ // eslint-disable-next-line prefer-const
74
+ let panelRef: HTMLDivElement | undefined = undefined
75
+
76
+ const [isOpen, setIsOpen] = useLocalStorage(
77
+ 'tanstackRouterDevtoolsOpen',
78
+ initialIsOpen,
79
+ )
80
+
81
+ const [devtoolsHeight, setDevtoolsHeight] = useLocalStorage<number | null>(
82
+ 'tanstackRouterDevtoolsHeight',
83
+ null,
84
+ )
85
+
86
+ const [isResolvedOpen, setIsResolvedOpen] = createSignal(false)
87
+ const [isResizing, setIsResizing] = createSignal(false)
88
+ const isMounted = useIsMounted()
89
+ const styles = useStyles()
90
+
91
+ const handleDragStart = (
92
+ panelElement: HTMLDivElement | undefined,
93
+ startEvent: any,
94
+ ) => {
95
+ if (startEvent.button !== 0) return // Only allow left click for drag
96
+
97
+ setIsResizing(true)
98
+
99
+ const dragInfo = {
100
+ originalHeight: panelElement?.getBoundingClientRect().height ?? 0,
101
+ pageY: startEvent.pageY,
102
+ }
103
+
104
+ const run = (moveEvent: MouseEvent) => {
105
+ const delta = dragInfo.pageY - moveEvent.pageY
106
+ const newHeight = dragInfo.originalHeight + delta
107
+
108
+ setDevtoolsHeight(newHeight)
109
+
110
+ if (newHeight < 70) {
111
+ setIsOpen(false)
112
+ } else {
113
+ setIsOpen(true)
114
+ }
115
+ }
116
+
117
+ const unsub = () => {
118
+ setIsResizing(false)
119
+ document.removeEventListener('mousemove', run)
120
+ document.removeEventListener('mouseUp', unsub)
121
+ }
122
+
123
+ document.addEventListener('mousemove', run)
124
+ document.addEventListener('mouseup', unsub)
125
+ }
126
+
127
+ const isButtonClosed = isOpen() ?? false
128
+
129
+ createEffect(() => {
130
+ setIsResolvedOpen(isOpen() ?? false)
131
+ })
132
+
133
+ createEffect(() => {
134
+ if (isResolvedOpen()) {
135
+ const previousValue = rootEl()?.parentElement?.style.paddingBottom
136
+
137
+ const run = () => {
138
+ const containerHeight = panelRef!.getBoundingClientRect().height
139
+ if (rootEl()?.parentElement) {
140
+ setRootEl((prev) => {
141
+ if (prev?.parentElement) {
142
+ prev.parentElement.style.paddingBottom = `${containerHeight}px`
143
+ }
144
+ return prev
145
+ })
146
+ }
147
+ }
148
+
149
+ run()
150
+
151
+ if (typeof window !== 'undefined') {
152
+ window.addEventListener('resize', run)
153
+
154
+ return () => {
155
+ window.removeEventListener('resize', run)
156
+ if (rootEl()?.parentElement && typeof previousValue === 'string') {
157
+ setRootEl((prev) => {
158
+ prev!.parentElement!.style.paddingBottom = previousValue
159
+ return prev
160
+ })
161
+ }
162
+ }
163
+ }
164
+ }
165
+ return
166
+ })
167
+
168
+ createEffect(() => {
169
+ if (rootEl()) {
170
+ const el = rootEl()
171
+ const fontSize = getComputedStyle(el!).fontSize
172
+ el?.style.setProperty('--tsrd-font-size', fontSize)
173
+ }
174
+ })
175
+
176
+ const { style: panelStyle = {}, ...otherPanelProps } = panelProps as {
177
+ style?: Record<string, any>
178
+ }
179
+
180
+ const {
181
+ style: closeButtonStyle = {},
182
+ onClick: onCloseClick,
183
+ ...otherCloseButtonProps
184
+ } = closeButtonProps
185
+
186
+ const {
187
+ onClick: onToggleClick,
188
+ class: toggleButtonClassName,
189
+ ...otherToggleButtonProps
190
+ } = toggleButtonProps
191
+
192
+ // Do not render on the server
193
+ if (!isMounted()) return null
194
+
195
+ const resolvedHeight = createMemo(() => devtoolsHeight() ?? 500)
196
+
197
+ const basePanelClass = createMemo(() => {
198
+ return cx(
199
+ styles().devtoolsPanelContainer,
200
+ styles().devtoolsPanelContainerVisibility(!!isOpen()),
201
+ styles().devtoolsPanelContainerResizing(isResizing),
202
+ styles().devtoolsPanelContainerAnimation(
203
+ isResolvedOpen(),
204
+ resolvedHeight() + 16,
205
+ ),
206
+ )
207
+ })
208
+
209
+ const basePanelStyle = createMemo(() => {
210
+ return {
211
+ height: `${resolvedHeight()}px`,
212
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
213
+ ...(panelStyle || {}),
214
+ }
215
+ })
216
+
217
+ const buttonStyle = createMemo(() => {
218
+ return cx(
219
+ styles().mainCloseBtn,
220
+ styles().mainCloseBtnPosition(position),
221
+ styles().mainCloseBtnAnimation(!!isOpen()),
222
+ toggleButtonClassName,
223
+ )
224
+ })
225
+
226
+ return (
227
+ <Dynamic
228
+ component={Container}
229
+ ref={setRootEl}
230
+ class="TanStackRouterDevtools"
231
+ >
232
+ <DevtoolsOnCloseContext.Provider
233
+ value={{
234
+ onCloseClick: onCloseClick ?? (() => {}),
235
+ }}
236
+ >
237
+ {/* {router() ? ( */}
238
+ <BaseTanStackRouterDevtoolsPanel
239
+ ref={panelRef as any}
240
+ {...otherPanelProps}
241
+ router={router}
242
+ routerState={routerState}
243
+ className={basePanelClass}
244
+ style={basePanelStyle}
245
+ isOpen={isResolvedOpen()}
246
+ setIsOpen={setIsOpen}
247
+ handleDragStart={(e) => handleDragStart(panelRef, e)}
248
+ shadowDOMTarget={shadowDOMTarget}
249
+ />
250
+ {/* ) : (
251
+ <p>No router</p>
252
+ )} */}
253
+ </DevtoolsOnCloseContext.Provider>
254
+
255
+ <button
256
+ type="button"
257
+ {...otherToggleButtonProps}
258
+ aria-label="Open TanStack Router Devtools"
259
+ onClick={(e) => {
260
+ setIsOpen(true)
261
+ onToggleClick && onToggleClick(e)
262
+ }}
263
+ class={buttonStyle()}
264
+ >
265
+ <div class={styles().mainCloseBtnIconContainer}>
266
+ <div class={styles().mainCloseBtnIconOuter}>
267
+ <TanStackLogo />
268
+ </div>
269
+ <div class={styles().mainCloseBtnIconInner}>
270
+ <TanStackLogo />
271
+ </div>
272
+ </div>
273
+ <div class={styles().mainCloseBtnDivider}>-</div>
274
+ <div class={styles().routerLogoCloseButton}>TanStack Router</div>
275
+ </button>
276
+ </Dynamic>
277
+ )
278
+ }
279
+
280
+ export default FloatingTanStackRouterDevtools