@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.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/AgeTicker.cjs +47 -0
- package/dist/cjs/AgeTicker.cjs.map +1 -0
- package/dist/cjs/AgeTicker.d.cts +6 -0
- package/dist/cjs/BaseTanStackRouterDevtoolsPanel.cjs +505 -0
- package/dist/cjs/BaseTanStackRouterDevtoolsPanel.cjs.map +1 -0
- package/dist/cjs/BaseTanStackRouterDevtoolsPanel.d.cts +34 -0
- package/dist/cjs/Explorer.cjs +307 -0
- package/dist/cjs/Explorer.cjs.map +1 -0
- package/dist/cjs/Explorer.d.cts +43 -0
- package/dist/cjs/FloatingTanStackRouterDevtools.cjs +195 -0
- package/dist/cjs/FloatingTanStackRouterDevtools.cjs.map +1 -0
- package/dist/cjs/FloatingTanStackRouterDevtools.d.cts +48 -0
- package/dist/cjs/TanStackRouterDevtoolsCore.cjs +99 -0
- package/dist/cjs/TanStackRouterDevtoolsCore.cjs.map +1 -0
- package/dist/cjs/TanStackRouterDevtoolsCore.d.cts +55 -0
- package/dist/cjs/TanStackRouterDevtoolsPanelCore.cjs +99 -0
- package/dist/cjs/TanStackRouterDevtoolsPanelCore.cjs.map +1 -0
- package/dist/cjs/TanStackRouterDevtoolsPanelCore.d.cts +43 -0
- package/dist/cjs/context.cjs +20 -0
- package/dist/cjs/context.cjs.map +1 -0
- package/dist/cjs/context.d.cts +13 -0
- package/dist/cjs/index.cjs +7 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/logo.cjs +92 -0
- package/dist/cjs/logo.cjs.map +1 -0
- package/dist/cjs/logo.d.cts +1 -0
- package/dist/cjs/theme.d.cts +34 -0
- package/dist/cjs/tokens.cjs +201 -0
- package/dist/cjs/tokens.cjs.map +1 -0
- package/dist/cjs/tokens.d.cts +298 -0
- package/dist/cjs/useLocalStorage.cjs +42 -0
- package/dist/cjs/useLocalStorage.cjs.map +1 -0
- package/dist/cjs/useLocalStorage.d.cts +2 -0
- package/dist/cjs/useMediaQuery.d.cts +2 -0
- package/dist/cjs/useStyles.cjs +582 -0
- package/dist/cjs/useStyles.cjs.map +1 -0
- package/dist/cjs/useStyles.d.cts +53 -0
- package/dist/cjs/utils.cjs +63 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +25 -0
- package/dist/esm/AgeTicker.d.ts +6 -0
- package/dist/esm/AgeTicker.js +47 -0
- package/dist/esm/AgeTicker.js.map +1 -0
- package/dist/esm/BaseTanStackRouterDevtoolsPanel.d.ts +34 -0
- package/dist/esm/BaseTanStackRouterDevtoolsPanel.js +505 -0
- package/dist/esm/BaseTanStackRouterDevtoolsPanel.js.map +1 -0
- package/dist/esm/Explorer.d.ts +43 -0
- package/dist/esm/Explorer.js +290 -0
- package/dist/esm/Explorer.js.map +1 -0
- package/dist/esm/FloatingTanStackRouterDevtools.d.ts +48 -0
- package/dist/esm/FloatingTanStackRouterDevtools.js +195 -0
- package/dist/esm/FloatingTanStackRouterDevtools.js.map +1 -0
- package/dist/esm/TanStackRouterDevtoolsCore.d.ts +55 -0
- package/dist/esm/TanStackRouterDevtoolsCore.js +99 -0
- package/dist/esm/TanStackRouterDevtoolsCore.js.map +1 -0
- package/dist/esm/TanStackRouterDevtoolsPanelCore.d.ts +43 -0
- package/dist/esm/TanStackRouterDevtoolsPanelCore.js +99 -0
- package/dist/esm/TanStackRouterDevtoolsPanelCore.js.map +1 -0
- package/dist/esm/context.d.ts +13 -0
- package/dist/esm/context.js +20 -0
- package/dist/esm/context.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logo.d.ts +1 -0
- package/dist/esm/logo.js +92 -0
- package/dist/esm/logo.js.map +1 -0
- package/dist/esm/theme.d.ts +34 -0
- package/dist/esm/tokens.d.ts +298 -0
- package/dist/esm/tokens.js +201 -0
- package/dist/esm/tokens.js.map +1 -0
- package/dist/esm/useLocalStorage.d.ts +2 -0
- package/dist/esm/useLocalStorage.js +43 -0
- package/dist/esm/useLocalStorage.js.map +1 -0
- package/dist/esm/useMediaQuery.d.ts +2 -0
- package/dist/esm/useStyles.d.ts +53 -0
- package/dist/esm/useStyles.js +565 -0
- package/dist/esm/useStyles.js.map +1 -0
- package/dist/esm/utils.d.ts +25 -0
- package/dist/esm/utils.js +63 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +71 -0
- package/src/AgeTicker.tsx +59 -0
- package/src/BaseTanStackRouterDevtoolsPanel.tsx +559 -0
- package/src/Explorer.tsx +339 -0
- package/src/FloatingTanStackRouterDevtools.tsx +280 -0
- package/src/TanStackRouterDevtoolsCore.tsx +139 -0
- package/src/TanStackRouterDevtoolsPanelCore.tsx +120 -0
- package/src/context.ts +24 -0
- package/src/index.tsx +2 -0
- package/src/logo.tsx +817 -0
- package/src/theme.tsx +36 -0
- package/src/tokens.ts +305 -0
- package/src/useLocalStorage.ts +52 -0
- package/src/useMediaQuery.ts +44 -0
- package/src/useStyles.tsx +589 -0
- package/src/utils.tsx +185 -0
package/src/Explorer.tsx
ADDED
|
@@ -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
|