@tanstack/pacer-devtools 1.1.5 → 1.2.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/dist/{components-DDmnxkL2.js → components-CGEHIK6F.js} +344 -231
- package/dist/{components-ByKT1JiI.js → components-vap4_VJT.js} +267 -166
- package/dist/index.d.ts +4 -8
- package/dist/index.js +3 -1
- package/dist/production/index.d.ts +3 -15
- package/dist/production/index.js +3 -1
- package/dist/production/server.js +3 -1
- package/dist/server.js +3 -1
- package/package.json +6 -6
- package/src/PacerContextProvider.tsx +96 -127
- package/src/components/ActionButtons.tsx +57 -33
- package/src/components/DetailsPanel.tsx +2 -1
- package/src/components/Shell.tsx +103 -34
- package/src/components/StateHeader.tsx +96 -66
- package/src/components/UtilStateJsonTree.tsx +25 -0
- package/src/core.tsx +4 -4
- package/src/index.ts +1 -1
- package/src/styles/use-styles.ts +22 -1
- package/src/utils/read-pacer-store-state.ts +33 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/pacer-devtools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Devtools for Pacer.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,17 +73,17 @@
|
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@tanstack/devtools-ui": "0.5.1",
|
|
75
75
|
"@tanstack/devtools-utils": "^0.4.0",
|
|
76
|
-
"@tanstack/solid-store": "^0.
|
|
76
|
+
"@tanstack/solid-store": "^0.9.2",
|
|
77
77
|
"clsx": "^2.1.1",
|
|
78
|
-
"csstype": "^3.
|
|
79
|
-
"dayjs": "^1.11.
|
|
78
|
+
"csstype": "^3.2.3",
|
|
79
|
+
"dayjs": "^1.11.20",
|
|
80
80
|
"goober": "^2.1.18",
|
|
81
81
|
"solid-js": "^1.9.11"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"rolldown-plugin-solid": "^0.2.1",
|
|
85
|
-
"tsdown": "^0.21.
|
|
86
|
-
"vite-plugin-solid": "^2.11.
|
|
85
|
+
"tsdown": "^0.21.4",
|
|
86
|
+
"vite-plugin-solid": "^2.11.11"
|
|
87
87
|
},
|
|
88
88
|
"scripts": {
|
|
89
89
|
"clean": "premove ./build ./dist",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { createStore } from 'solid-js/store'
|
|
1
|
+
import { createStore, produce } from 'solid-js/store'
|
|
2
2
|
import { createContext, createEffect, onCleanup, useContext } from 'solid-js'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getPacerDevtoolsInstance,
|
|
5
|
+
pacerEventClient,
|
|
6
|
+
} from '@tanstack/pacer/event-client'
|
|
4
7
|
import type {
|
|
5
8
|
AsyncBatcher,
|
|
6
9
|
AsyncDebouncer,
|
|
@@ -9,6 +12,7 @@ import type {
|
|
|
9
12
|
AsyncThrottler,
|
|
10
13
|
Batcher,
|
|
11
14
|
Debouncer,
|
|
15
|
+
PacerEventName,
|
|
12
16
|
Queuer,
|
|
13
17
|
RateLimiter,
|
|
14
18
|
Throttler,
|
|
@@ -49,18 +53,62 @@ const PacerDevtoolsContext = createContext<
|
|
|
49
53
|
]
|
|
50
54
|
>([initialPacerDevtoolsStore, () => {}])
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
type UtilListKey = Exclude<keyof PacerDevtoolsContextType, 'lastUpdatedByKey'>
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per
|
|
60
|
+
* event suffix so the same `pacer:${suffix}` channel the library emits on is
|
|
61
|
+
* observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is
|
|
62
|
+
* not always relayed the same way by the shell.
|
|
63
|
+
*
|
|
64
|
+
* `d-*` suffixes are used when the devtools panel pushes state back (see
|
|
65
|
+
* ActionButtons); those must update the panel store too.
|
|
66
|
+
*/
|
|
67
|
+
const PACER_DEVTOOLS_UTIL_EVENTS: Array<{
|
|
68
|
+
listKey: UtilListKey
|
|
69
|
+
suffixes: ReadonlyArray<PacerEventName>
|
|
70
|
+
}> = [
|
|
71
|
+
{
|
|
72
|
+
listKey: 'asyncBatchers',
|
|
73
|
+
suffixes: ['AsyncBatcher', 'd-AsyncBatcher'],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
listKey: 'asyncDebouncers',
|
|
77
|
+
suffixes: ['AsyncDebouncer', 'd-AsyncDebouncer'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
listKey: 'asyncQueuers',
|
|
81
|
+
suffixes: ['AsyncQueuer', 'd-AsyncQueuer'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
listKey: 'asyncRateLimiters',
|
|
85
|
+
suffixes: ['AsyncRateLimiter', 'd-AsyncRateLimiter'],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
listKey: 'asyncThrottlers',
|
|
89
|
+
suffixes: ['AsyncThrottler', 'd-AsyncThrottler'],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
listKey: 'batchers',
|
|
93
|
+
suffixes: ['Batcher', 'd-Batcher'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
listKey: 'debouncers',
|
|
97
|
+
suffixes: ['Debouncer', 'd-Debouncer'],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
listKey: 'queuers',
|
|
101
|
+
suffixes: ['Queuer', 'd-Queuer'],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
listKey: 'rateLimiters',
|
|
105
|
+
suffixes: ['RateLimiter', 'd-RateLimiter'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
listKey: 'throttlers',
|
|
109
|
+
suffixes: ['Throttler', 'd-Throttler'],
|
|
110
|
+
},
|
|
111
|
+
]
|
|
64
112
|
|
|
65
113
|
export function PacerContextProvider(props: { children: any }) {
|
|
66
114
|
const [store, setStore] = createStore<PacerDevtoolsContextType>(
|
|
@@ -68,121 +116,42 @@ export function PacerContextProvider(props: { children: any }) {
|
|
|
68
116
|
)
|
|
69
117
|
|
|
70
118
|
createEffect(() => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
break
|
|
106
|
-
}
|
|
107
|
-
case 'pacer:AsyncRateLimiter': {
|
|
108
|
-
setStore({
|
|
109
|
-
asyncRateLimiters: updateOrAddToArray(
|
|
110
|
-
store.asyncRateLimiters,
|
|
111
|
-
e.payload,
|
|
112
|
-
),
|
|
113
|
-
lastUpdatedByKey: {
|
|
114
|
-
...store.lastUpdatedByKey,
|
|
115
|
-
[e.payload.key]: Date.now(),
|
|
116
|
-
},
|
|
117
|
-
})
|
|
118
|
-
break
|
|
119
|
-
}
|
|
120
|
-
case 'pacer:AsyncThrottler': {
|
|
121
|
-
setStore({
|
|
122
|
-
asyncThrottlers: updateOrAddToArray(
|
|
123
|
-
store.asyncThrottlers,
|
|
124
|
-
e.payload,
|
|
125
|
-
),
|
|
126
|
-
lastUpdatedByKey: {
|
|
127
|
-
...store.lastUpdatedByKey,
|
|
128
|
-
[e.payload.key]: Date.now(),
|
|
129
|
-
},
|
|
130
|
-
})
|
|
131
|
-
break
|
|
132
|
-
}
|
|
133
|
-
case 'pacer:Batcher': {
|
|
134
|
-
setStore({
|
|
135
|
-
batchers: updateOrAddToArray(store.batchers, e.payload),
|
|
136
|
-
lastUpdatedByKey: {
|
|
137
|
-
...store.lastUpdatedByKey,
|
|
138
|
-
[e.payload.key]: Date.now(),
|
|
139
|
-
},
|
|
140
|
-
})
|
|
141
|
-
break
|
|
142
|
-
}
|
|
143
|
-
case 'pacer:Debouncer': {
|
|
144
|
-
setStore({
|
|
145
|
-
debouncers: updateOrAddToArray(store.debouncers, e.payload),
|
|
146
|
-
lastUpdatedByKey: {
|
|
147
|
-
...store.lastUpdatedByKey,
|
|
148
|
-
[e.payload.key]: Date.now(),
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
break
|
|
152
|
-
}
|
|
153
|
-
case 'pacer:Queuer': {
|
|
154
|
-
setStore({
|
|
155
|
-
queuers: updateOrAddToArray(store.queuers, e.payload),
|
|
156
|
-
lastUpdatedByKey: {
|
|
157
|
-
...store.lastUpdatedByKey,
|
|
158
|
-
[e.payload.key]: Date.now(),
|
|
159
|
-
},
|
|
160
|
-
})
|
|
161
|
-
break
|
|
162
|
-
}
|
|
163
|
-
case 'pacer:RateLimiter': {
|
|
164
|
-
setStore({
|
|
165
|
-
rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload),
|
|
166
|
-
lastUpdatedByKey: {
|
|
167
|
-
...store.lastUpdatedByKey,
|
|
168
|
-
[e.payload.key]: Date.now(),
|
|
169
|
-
},
|
|
170
|
-
})
|
|
171
|
-
break
|
|
172
|
-
}
|
|
173
|
-
case 'pacer:Throttler': {
|
|
174
|
-
setStore({
|
|
175
|
-
throttlers: updateOrAddToArray(store.throttlers, e.payload),
|
|
176
|
-
lastUpdatedByKey: {
|
|
177
|
-
...store.lastUpdatedByKey,
|
|
178
|
-
[e.payload.key]: Date.now(),
|
|
179
|
-
},
|
|
180
|
-
})
|
|
181
|
-
break
|
|
182
|
-
}
|
|
119
|
+
const cleanups: Array<() => void> = []
|
|
120
|
+
|
|
121
|
+
for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) {
|
|
122
|
+
for (const suffix of suffixes) {
|
|
123
|
+
cleanups.push(
|
|
124
|
+
pacerEventClient.on(suffix, (e) => {
|
|
125
|
+
const payload = e.payload
|
|
126
|
+
const key = payload.key
|
|
127
|
+
if (!key) return
|
|
128
|
+
|
|
129
|
+
const instance = getPacerDevtoolsInstance(key)
|
|
130
|
+
if (!instance || typeof instance !== 'object') return
|
|
131
|
+
|
|
132
|
+
setStore(
|
|
133
|
+
produce((draft) => {
|
|
134
|
+
const list = draft[listKey] as Array<{ key: string }>
|
|
135
|
+
const index = list.findIndex((item) => item.key === key)
|
|
136
|
+
const inst = instance as { key: string }
|
|
137
|
+
if (index !== -1) {
|
|
138
|
+
list[index] = inst as (typeof list)[number]
|
|
139
|
+
} else {
|
|
140
|
+
list.push(inst as (typeof list)[number])
|
|
141
|
+
}
|
|
142
|
+
draft.lastUpdatedByKey[key] = Date.now()
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
}),
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
onCleanup(() => {
|
|
151
|
+
for (const cleanup of cleanups) {
|
|
152
|
+
cleanup()
|
|
183
153
|
}
|
|
184
154
|
})
|
|
185
|
-
onCleanup(cleanup)
|
|
186
155
|
})
|
|
187
156
|
return (
|
|
188
157
|
<PacerDevtoolsContext.Provider value={[store, setStore]}>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emitChange } from '@tanstack/pacer'
|
|
2
|
+
import { useStore } from '@tanstack/solid-store'
|
|
2
3
|
import { useStyles } from '../styles/use-styles'
|
|
4
|
+
import {
|
|
5
|
+
getPacerUtilStoreState,
|
|
6
|
+
isPacerUtilTanStackStore,
|
|
7
|
+
} from '../utils/read-pacer-store-state'
|
|
3
8
|
import type { PacerEventName } from '@tanstack/pacer'
|
|
4
9
|
|
|
5
10
|
type ActionButtonsProps = {
|
|
@@ -10,11 +15,30 @@ type ActionButtonsProps = {
|
|
|
10
15
|
export function ActionButtons(props: ActionButtonsProps) {
|
|
11
16
|
const styles = useStyles()
|
|
12
17
|
const utilInstance = props.instance
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const store = utilInstance?.store
|
|
19
|
+
|
|
20
|
+
const stateAccessor = isPacerUtilTanStackStore(store)
|
|
21
|
+
? useStore(store as never, (s: unknown) =>
|
|
22
|
+
s !== null && s !== undefined && typeof s === 'object'
|
|
23
|
+
? (s as Record<string, unknown>)
|
|
24
|
+
: {},
|
|
25
|
+
)
|
|
26
|
+
: () => getPacerUtilStoreState(utilInstance) as Record<string, unknown>
|
|
27
|
+
|
|
28
|
+
const getState = () => stateAccessor()
|
|
29
|
+
|
|
30
|
+
const hasPending = () => {
|
|
31
|
+
const u = getState()
|
|
32
|
+
return 'isPending' in u
|
|
33
|
+
}
|
|
34
|
+
const isPending = () => {
|
|
35
|
+
const u = getState()
|
|
36
|
+
return 'isPending' in u ? !!u.isPending : false
|
|
37
|
+
}
|
|
38
|
+
const isEmptyFlag = () => {
|
|
39
|
+
const u = getState()
|
|
40
|
+
return 'isEmpty' in u ? !!u.isEmpty : false
|
|
41
|
+
}
|
|
18
42
|
|
|
19
43
|
const hasFlush = typeof utilInstance.flush === 'function'
|
|
20
44
|
const hasCancel = typeof utilInstance.cancel === 'function'
|
|
@@ -24,9 +48,12 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
24
48
|
const hasStop = typeof utilInstance.stop === 'function'
|
|
25
49
|
const hasStartStop = hasStart && hasStop
|
|
26
50
|
|
|
27
|
-
const isRunning =
|
|
51
|
+
const isRunning = () => {
|
|
52
|
+
const u = getState()
|
|
53
|
+
return 'isRunning' in u ? !!u.isRunning : true
|
|
54
|
+
}
|
|
28
55
|
|
|
29
|
-
if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) {
|
|
56
|
+
if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) {
|
|
30
57
|
return (
|
|
31
58
|
<div class={styles().sectionEmpty}>
|
|
32
59
|
No actions available for this util
|
|
@@ -35,23 +62,24 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
35
62
|
}
|
|
36
63
|
|
|
37
64
|
const emitName = `d-${props.utilName}` as PacerEventName
|
|
65
|
+
const utilNameLower = props.utilName.toLowerCase()
|
|
38
66
|
|
|
39
67
|
return (
|
|
40
68
|
<div class={styles().actionsRow}>
|
|
41
|
-
{hasPending && (
|
|
69
|
+
{hasPending() && (
|
|
42
70
|
<button
|
|
43
71
|
class={styles().actionButton}
|
|
44
72
|
onMouseDown={() => {
|
|
45
|
-
const next = !isPending
|
|
73
|
+
const next = !isPending()
|
|
46
74
|
utilInstance.store.setState((prev: any) => ({
|
|
47
75
|
...prev,
|
|
48
76
|
isPending: next,
|
|
49
77
|
}))
|
|
50
|
-
|
|
78
|
+
emitChange(emitName, utilInstance)
|
|
51
79
|
}}
|
|
52
80
|
>
|
|
53
81
|
<span class={styles().actionDotBlue} />
|
|
54
|
-
{isPending ? 'Restore Pending' : 'Trigger Pending'}
|
|
82
|
+
{isPending() ? 'Restore Pending' : 'Trigger Pending'}
|
|
55
83
|
</button>
|
|
56
84
|
)}
|
|
57
85
|
{hasFlush && (
|
|
@@ -59,15 +87,13 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
59
87
|
class={styles().actionButton}
|
|
60
88
|
onMouseDown={() => {
|
|
61
89
|
utilInstance.flush()
|
|
62
|
-
|
|
90
|
+
emitChange(emitName, utilInstance)
|
|
63
91
|
}}
|
|
64
92
|
disabled={
|
|
65
|
-
(
|
|
66
|
-
|
|
67
|
-
(
|
|
68
|
-
|
|
69
|
-
(props.utilName.toLowerCase().includes('batcher') && isEmpty) ||
|
|
70
|
-
(props.utilName.toLowerCase().includes('queuer') && isEmpty)
|
|
93
|
+
(utilNameLower.includes('debouncer') && !isPending()) ||
|
|
94
|
+
(utilNameLower.includes('throttler') && !isPending()) ||
|
|
95
|
+
(utilNameLower.includes('batcher') && isEmptyFlag()) ||
|
|
96
|
+
(utilNameLower.includes('queuer') && isEmptyFlag())
|
|
71
97
|
}
|
|
72
98
|
>
|
|
73
99
|
<span class={styles().actionDotGreen} />
|
|
@@ -79,15 +105,13 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
79
105
|
class={styles().actionButton}
|
|
80
106
|
onMouseDown={() => {
|
|
81
107
|
utilInstance.cancel()
|
|
82
|
-
|
|
108
|
+
emitChange(emitName, utilInstance)
|
|
83
109
|
}}
|
|
84
110
|
disabled={
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
(
|
|
88
|
-
|
|
89
|
-
(props.utilName.toLowerCase().includes('batcher') && isEmpty) ||
|
|
90
|
-
(props.utilName.toLowerCase().includes('queuer') && isEmpty)
|
|
111
|
+
(utilNameLower.includes('debouncer') && !isPending()) ||
|
|
112
|
+
(utilNameLower.includes('throttler') && !isPending()) ||
|
|
113
|
+
(utilNameLower.includes('batcher') && isEmptyFlag()) ||
|
|
114
|
+
(utilNameLower.includes('queuer') && isEmptyFlag())
|
|
91
115
|
}
|
|
92
116
|
>
|
|
93
117
|
<span class={styles().actionDotRed} />
|
|
@@ -99,7 +123,7 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
99
123
|
class={styles().actionButton}
|
|
100
124
|
onMouseDown={() => {
|
|
101
125
|
utilInstance.reset()
|
|
102
|
-
|
|
126
|
+
emitChange(emitName, utilInstance)
|
|
103
127
|
}}
|
|
104
128
|
>
|
|
105
129
|
<span class={styles().actionDotYellow} />
|
|
@@ -111,11 +135,11 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
111
135
|
class={styles().actionButton}
|
|
112
136
|
onMouseDown={() => {
|
|
113
137
|
utilInstance.clear()
|
|
114
|
-
|
|
138
|
+
emitChange(emitName, utilInstance)
|
|
115
139
|
}}
|
|
116
140
|
disabled={
|
|
117
|
-
(
|
|
118
|
-
(
|
|
141
|
+
(utilNameLower.includes('batcher') && isEmptyFlag()) ||
|
|
142
|
+
(utilNameLower.includes('queuer') && isEmptyFlag())
|
|
119
143
|
}
|
|
120
144
|
>
|
|
121
145
|
<span class={styles().actionDotOrange} />
|
|
@@ -126,16 +150,16 @@ export function ActionButtons(props: ActionButtonsProps) {
|
|
|
126
150
|
<button
|
|
127
151
|
class={styles().actionButton}
|
|
128
152
|
onMouseDown={() => {
|
|
129
|
-
if (isRunning) {
|
|
153
|
+
if (isRunning()) {
|
|
130
154
|
utilInstance.stop()
|
|
131
155
|
} else {
|
|
132
156
|
utilInstance.start()
|
|
133
157
|
}
|
|
134
|
-
|
|
158
|
+
emitChange(emitName, utilInstance)
|
|
135
159
|
}}
|
|
136
160
|
>
|
|
137
161
|
<span class={styles().actionDotPurple} />
|
|
138
|
-
{isRunning ? 'Stop' : 'Start'}
|
|
162
|
+
{isRunning() ? 'Stop' : 'Start'}
|
|
139
163
|
</button>
|
|
140
164
|
)}
|
|
141
165
|
</div>
|
|
@@ -2,6 +2,7 @@ import { JsonTree } from '@tanstack/devtools-ui'
|
|
|
2
2
|
import { useStyles } from '../styles/use-styles'
|
|
3
3
|
import { ActionButtons } from './ActionButtons'
|
|
4
4
|
import { StateHeader } from './StateHeader'
|
|
5
|
+
import { UtilStateJsonTree } from './UtilStateJsonTree'
|
|
5
6
|
|
|
6
7
|
type DetailsPanelProps = {
|
|
7
8
|
selectedInstance: () => { instance: any; type: string } | null
|
|
@@ -42,7 +43,7 @@ export function DetailsPanel(props: DetailsPanelProps) {
|
|
|
42
43
|
<div class={styles().detailSection}>
|
|
43
44
|
<div class={styles().detailSectionHeader}>State</div>
|
|
44
45
|
<div class={styles().stateContent}>
|
|
45
|
-
<
|
|
46
|
+
<UtilStateJsonTree instance={entry.instance} />
|
|
46
47
|
</div>
|
|
47
48
|
</div>
|
|
48
49
|
|
package/src/components/Shell.tsx
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createEffect,
|
|
3
|
+
createMemo,
|
|
4
|
+
createSignal,
|
|
5
|
+
onCleanup,
|
|
6
|
+
onMount,
|
|
7
|
+
} from 'solid-js'
|
|
2
8
|
import { Header, HeaderLogo, MainPanel } from '@tanstack/devtools-ui'
|
|
3
9
|
import { useStyles } from '../styles/use-styles'
|
|
4
10
|
import { usePacerDevtoolsState } from '../PacerContextProvider'
|
|
@@ -7,6 +13,23 @@ import { UtilList } from './UtilList'
|
|
|
7
13
|
import { DetailsPanel } from './DetailsPanel'
|
|
8
14
|
import type { StateKey } from './util-groups'
|
|
9
15
|
|
|
16
|
+
function readResizeObserverBlockSize(entry: ResizeObserverEntry): number {
|
|
17
|
+
const [box] = entry.borderBoxSize
|
|
18
|
+
if (box !== undefined) return box.blockSize
|
|
19
|
+
return entry.contentRect.height
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
|
|
23
|
+
function resolvePluginHost(el: HTMLElement): HTMLElement | null {
|
|
24
|
+
const byId = el.closest<HTMLElement>('[id^="plugin-container-"]')
|
|
25
|
+
if (byId) return byId
|
|
26
|
+
return (
|
|
27
|
+
el.parentElement?.parentElement?.parentElement ??
|
|
28
|
+
el.parentElement?.parentElement ??
|
|
29
|
+
el.parentElement
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
10
33
|
export function Shell() {
|
|
11
34
|
const styles = useStyles()
|
|
12
35
|
const state = usePacerDevtoolsState()
|
|
@@ -14,6 +37,12 @@ export function Shell() {
|
|
|
14
37
|
const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
|
|
15
38
|
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300)
|
|
16
39
|
const [isDragging, setIsDragging] = createSignal(false)
|
|
40
|
+
const [shellRootEl, setShellRootEl] = createSignal<HTMLDivElement | null>(
|
|
41
|
+
null,
|
|
42
|
+
)
|
|
43
|
+
const [slotHeightPx, setSlotHeightPx] = createSignal<number | undefined>(
|
|
44
|
+
undefined,
|
|
45
|
+
)
|
|
17
46
|
|
|
18
47
|
const selectedInstance = createMemo(() => {
|
|
19
48
|
const key = selectedKey()
|
|
@@ -65,43 +94,83 @@ export function Shell() {
|
|
|
65
94
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
66
95
|
})
|
|
67
96
|
|
|
97
|
+
/**
|
|
98
|
+
* TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
|
|
99
|
+
* ancestors, so percentage height often never constrains our tree. Observing the
|
|
100
|
+
* outer `plugin-container-*` host (not the immediate React mount wrapper) and
|
|
101
|
+
* setting an explicit pixel height makes inner overflow-y regions scroll without
|
|
102
|
+
* capping to the mount div’s collapsed height.
|
|
103
|
+
*/
|
|
104
|
+
createEffect(() => {
|
|
105
|
+
const el = shellRootEl()
|
|
106
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
107
|
+
const pluginSlot = resolvePluginHost(el)
|
|
108
|
+
if (!pluginSlot) return
|
|
109
|
+
|
|
110
|
+
const ro = new ResizeObserver((entries) => {
|
|
111
|
+
const entry = entries[0]
|
|
112
|
+
if (!entry) return
|
|
113
|
+
const target = entry.target as HTMLElement
|
|
114
|
+
const h =
|
|
115
|
+
target.clientHeight > 0
|
|
116
|
+
? target.clientHeight
|
|
117
|
+
: readResizeObserverBlockSize(entry)
|
|
118
|
+
if (h > 0) setSlotHeightPx(Math.round(h))
|
|
119
|
+
})
|
|
120
|
+
ro.observe(pluginSlot)
|
|
121
|
+
onCleanup(() => ro.disconnect())
|
|
122
|
+
})
|
|
123
|
+
|
|
68
124
|
return (
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
setSelectedKey={setSelectedKey}
|
|
88
|
-
utilState={utilState}
|
|
89
|
-
/>
|
|
90
|
-
</div>
|
|
125
|
+
<div
|
|
126
|
+
ref={setShellRootEl}
|
|
127
|
+
class={styles().shellRoot}
|
|
128
|
+
style={{
|
|
129
|
+
...(slotHeightPx() !== undefined
|
|
130
|
+
? {
|
|
131
|
+
height: `${slotHeightPx()}px`,
|
|
132
|
+
'max-height': `${slotHeightPx()}px`,
|
|
133
|
+
}
|
|
134
|
+
: {}),
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<MainPanel class={styles().mainPanelShell}>
|
|
138
|
+
<Header>
|
|
139
|
+
<HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}>
|
|
140
|
+
TanStack Pacer
|
|
141
|
+
</HeaderLogo>
|
|
142
|
+
</Header>
|
|
91
143
|
|
|
92
|
-
<div
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
144
|
+
<div class={styles().mainContainer}>
|
|
145
|
+
<div
|
|
146
|
+
class={styles().leftPanel}
|
|
147
|
+
style={{
|
|
148
|
+
width: `${leftPanelWidth()}px`,
|
|
149
|
+
'min-width': '150px',
|
|
150
|
+
'max-width': '800px',
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<UtilList
|
|
154
|
+
selectedKey={selectedKey}
|
|
155
|
+
setSelectedKey={setSelectedKey}
|
|
156
|
+
utilState={utilState}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
96
159
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
selectedInstance={selectedInstance}
|
|
101
|
-
utilState={utilState}
|
|
160
|
+
<div
|
|
161
|
+
class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`}
|
|
162
|
+
onMouseDown={handleMouseDown}
|
|
102
163
|
/>
|
|
164
|
+
|
|
165
|
+
<div class={styles().rightPanel} style={{ flex: 1 }}>
|
|
166
|
+
<div class={styles().panelHeader}>Details</div>
|
|
167
|
+
<DetailsPanel
|
|
168
|
+
selectedInstance={selectedInstance}
|
|
169
|
+
utilState={utilState}
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
103
172
|
</div>
|
|
104
|
-
</
|
|
105
|
-
</
|
|
173
|
+
</MainPanel>
|
|
174
|
+
</div>
|
|
106
175
|
)
|
|
107
176
|
}
|