@tanstack/hotkeys-devtools 0.4.4 → 0.5.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/HotkeysContextProvider.js +25 -2
- package/dist/HotkeysContextProvider.js.map +1 -1
- package/dist/components/DetailsPanel.js +84 -48
- package/dist/components/DetailsPanel.js.map +1 -1
- package/dist/components/HotkeyList.js +229 -195
- package/dist/components/HotkeyList.js.map +1 -1
- package/dist/components/Shell.js +100 -43
- package/dist/components/Shell.js.map +1 -1
- package/dist/sequence-progress.js +20 -0
- package/dist/sequence-progress.js.map +1 -0
- package/dist/styles/use-styles.js +47 -0
- package/dist/styles/use-styles.js.map +1 -1
- package/package.json +2 -2
- package/src/HotkeysContextProvider.tsx +35 -0
- package/src/components/DetailsPanel.tsx +65 -20
- package/src/components/HotkeyList.tsx +187 -151
- package/src/components/Shell.tsx +101 -34
- package/src/sequence-progress.ts +29 -0
- package/src/styles/use-styles.ts +49 -0
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 { useHotkeysDevtoolsState } from '../HotkeysContextProvider'
|
|
@@ -6,12 +12,33 @@ import { HeldKeysBar } from './HeldKeysTopbar'
|
|
|
6
12
|
import { HotkeyList } from './HotkeyList'
|
|
7
13
|
import { DetailsPanel } from './DetailsPanel'
|
|
8
14
|
|
|
15
|
+
function readResizeObserverBlockSize(entry: ResizeObserverEntry): number {
|
|
16
|
+
const [box] = entry.borderBoxSize
|
|
17
|
+
if (box !== undefined) return box.blockSize
|
|
18
|
+
return entry.contentRect.height
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** TanStack devtools plugin pane; percentage heights break without this. */
|
|
22
|
+
function resolvePluginHost(el: HTMLElement): HTMLElement | null {
|
|
23
|
+
const found = el.closest('[id^="plugin-container-"]')
|
|
24
|
+
if (found instanceof HTMLElement) return found
|
|
25
|
+
let n: HTMLElement | null = el
|
|
26
|
+
for (let i = 0; i < 3 && n; i++) {
|
|
27
|
+
n = n.parentElement
|
|
28
|
+
}
|
|
29
|
+
return n
|
|
30
|
+
}
|
|
31
|
+
|
|
9
32
|
export function Shell() {
|
|
10
33
|
const styles = useStyles()
|
|
11
34
|
const state = useHotkeysDevtoolsState()
|
|
12
35
|
const [selectedId, setSelectedId] = createSignal<string | null>(null)
|
|
13
36
|
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300)
|
|
14
37
|
const [isDragging, setIsDragging] = createSignal(false)
|
|
38
|
+
const [shellRootEl, setShellRootEl] = createSignal<HTMLElement | null>(null)
|
|
39
|
+
const [slotHeightPx, setSlotHeightPx] = createSignal<number | undefined>(
|
|
40
|
+
undefined,
|
|
41
|
+
)
|
|
15
42
|
|
|
16
43
|
const selectedRegistration = createMemo(() => {
|
|
17
44
|
const id = selectedId()
|
|
@@ -60,45 +87,85 @@ export function Shell() {
|
|
|
60
87
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
61
88
|
})
|
|
62
89
|
|
|
90
|
+
createEffect(() => {
|
|
91
|
+
const el = shellRootEl()
|
|
92
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
93
|
+
const pluginHost = resolvePluginHost(el)
|
|
94
|
+
if (!pluginHost) return
|
|
95
|
+
|
|
96
|
+
const applyHeight = (entry?: ResizeObserverEntry) => {
|
|
97
|
+
const clientH = pluginHost.clientHeight
|
|
98
|
+
const fromObserver =
|
|
99
|
+
entry !== undefined ? readResizeObserverBlockSize(entry) : 0
|
|
100
|
+
const h = clientH > 0 ? clientH : fromObserver
|
|
101
|
+
if (h > 0) setSlotHeightPx(Math.round(h))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
applyHeight()
|
|
105
|
+
const ro = new ResizeObserver((entries) => {
|
|
106
|
+
applyHeight(entries[0])
|
|
107
|
+
})
|
|
108
|
+
ro.observe(pluginHost)
|
|
109
|
+
onCleanup(() => ro.disconnect())
|
|
110
|
+
})
|
|
111
|
+
|
|
63
112
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
style={{
|
|
83
|
-
width: `${leftPanelWidth()}px`,
|
|
84
|
-
'min-width': '150px',
|
|
85
|
-
'max-width': '800px',
|
|
113
|
+
<div
|
|
114
|
+
ref={(el) => setShellRootEl(el)}
|
|
115
|
+
class={styles().shellRoot}
|
|
116
|
+
style={{
|
|
117
|
+
...(slotHeightPx() !== undefined
|
|
118
|
+
? {
|
|
119
|
+
height: `${slotHeightPx()}px`,
|
|
120
|
+
'max-height': `${slotHeightPx()}px`,
|
|
121
|
+
}
|
|
122
|
+
: {}),
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
<MainPanel class={styles().mainPanelShell}>
|
|
126
|
+
<Header>
|
|
127
|
+
<HeaderLogo
|
|
128
|
+
flavor={{
|
|
129
|
+
light: 'oklch(51.4% 0.222 16.935)',
|
|
130
|
+
dark: 'oklch(58.6% 0.253 17.585)',
|
|
86
131
|
}}
|
|
87
132
|
>
|
|
88
|
-
|
|
89
|
-
</
|
|
133
|
+
TanStack Hotkeys
|
|
134
|
+
</HeaderLogo>
|
|
135
|
+
</Header>
|
|
136
|
+
|
|
137
|
+
<div class={styles().mainContainer}>
|
|
138
|
+
<HeldKeysBar />
|
|
139
|
+
|
|
140
|
+
<div class={styles().panelsContainer}>
|
|
141
|
+
<div
|
|
142
|
+
class={styles().leftPanel}
|
|
143
|
+
style={{
|
|
144
|
+
width: `${leftPanelWidth()}px`,
|
|
145
|
+
'min-width': '150px',
|
|
146
|
+
'max-width': '800px',
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<div class={styles().leftPanelScroll}>
|
|
150
|
+
<HotkeyList
|
|
151
|
+
selectedId={selectedId}
|
|
152
|
+
setSelectedId={setSelectedId}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
90
156
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
157
|
+
<div
|
|
158
|
+
class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`}
|
|
159
|
+
onMouseDown={handleMouseDown}
|
|
160
|
+
/>
|
|
95
161
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
162
|
+
<div class={styles().rightPanel} style={{ flex: 1 }}>
|
|
163
|
+
<div class={styles().panelHeader}>Details</div>
|
|
164
|
+
<DetailsPanel selectedRegistration={selectedRegistration} />
|
|
165
|
+
</div>
|
|
99
166
|
</div>
|
|
100
167
|
</div>
|
|
101
|
-
</
|
|
102
|
-
</
|
|
168
|
+
</MainPanel>
|
|
169
|
+
</div>
|
|
103
170
|
)
|
|
104
171
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DEFAULT_SEQUENCE_TIMEOUT } from '@tanstack/hotkeys'
|
|
2
|
+
import type { SequenceRegistrationView } from '@tanstack/hotkeys'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Matched step count for UI, accounting for inter-key timeout even if the
|
|
6
|
+
* manager has not processed another event yet.
|
|
7
|
+
*/
|
|
8
|
+
export function effectiveSequenceMatchedSteps(
|
|
9
|
+
reg: SequenceRegistrationView,
|
|
10
|
+
now: number,
|
|
11
|
+
): number {
|
|
12
|
+
const timeout = reg.options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT
|
|
13
|
+
if (
|
|
14
|
+
reg.matchedStepCount <= 0 ||
|
|
15
|
+
reg.partialMatchLastKeyTime <= 0 ||
|
|
16
|
+
now - reg.partialMatchLastKeyTime > timeout
|
|
17
|
+
) {
|
|
18
|
+
return 0
|
|
19
|
+
}
|
|
20
|
+
return reg.matchedStepCount
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether the devtools should run a short interval to refresh sequence progress UI. */
|
|
24
|
+
export function needsSequenceProgressClock(
|
|
25
|
+
regs: Array<SequenceRegistrationView>,
|
|
26
|
+
now: number,
|
|
27
|
+
): boolean {
|
|
28
|
+
return regs.some((reg) => effectiveSequenceMatchedSteps(reg, now) > 0)
|
|
29
|
+
}
|
package/src/styles/use-styles.ts
CHANGED
|
@@ -10,9 +10,30 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
10
10
|
const t = (light: string, dark: string) => (theme === 'light' ? light : dark)
|
|
11
11
|
|
|
12
12
|
return {
|
|
13
|
+
/** Full-height flex child so MainPanel gets a bounded height from the portal */
|
|
14
|
+
shellRoot: css`
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
min-height: 0;
|
|
18
|
+
height: 100%;
|
|
19
|
+
flex: 1;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
`,
|
|
22
|
+
/** Overrides devtools-ui MainPanel (overflow-y: auto) so inner columns scroll independently */
|
|
23
|
+
mainPanelShell: css`
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
flex: 1 1 0%;
|
|
27
|
+
min-height: 0;
|
|
28
|
+
height: auto;
|
|
29
|
+
overflow: hidden !important;
|
|
30
|
+
`,
|
|
13
31
|
mainContainer: css`
|
|
14
32
|
display: flex;
|
|
15
33
|
flex-direction: column;
|
|
34
|
+
flex: 1 1 0%;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
overflow: hidden;
|
|
16
37
|
padding: ${size[2]};
|
|
17
38
|
padding-top: 0;
|
|
18
39
|
margin-top: ${size[2]};
|
|
@@ -47,6 +68,10 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
47
68
|
`,
|
|
48
69
|
panelsContainer: css`
|
|
49
70
|
display: flex;
|
|
71
|
+
flex: 1 1 0%;
|
|
72
|
+
min-height: 0;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
align-items: stretch;
|
|
50
75
|
`,
|
|
51
76
|
dragHandle: css`
|
|
52
77
|
width: 8px;
|
|
@@ -129,6 +154,13 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
129
154
|
display: flex;
|
|
130
155
|
flex-direction: column;
|
|
131
156
|
flex-shrink: 0;
|
|
157
|
+
min-height: 0;
|
|
158
|
+
min-width: 0;
|
|
159
|
+
`,
|
|
160
|
+
leftPanelScroll: css`
|
|
161
|
+
flex: 1;
|
|
162
|
+
min-height: 0;
|
|
163
|
+
overflow-y: auto;
|
|
132
164
|
`,
|
|
133
165
|
rightPanel: css`
|
|
134
166
|
background: ${t(colors.gray[100], colors.darkGray[800])};
|
|
@@ -137,6 +169,9 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
137
169
|
display: flex;
|
|
138
170
|
flex-direction: column;
|
|
139
171
|
flex: 1;
|
|
172
|
+
min-height: 0;
|
|
173
|
+
min-width: 0;
|
|
174
|
+
overflow: hidden;
|
|
140
175
|
`,
|
|
141
176
|
panelHeader: css`
|
|
142
177
|
font-size: ${fontSize.md};
|
|
@@ -198,6 +233,10 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
198
233
|
text-overflow: ellipsis;
|
|
199
234
|
white-space: nowrap;
|
|
200
235
|
`,
|
|
236
|
+
sequenceStepMatched: css`
|
|
237
|
+
font-weight: ${font.weight.bold};
|
|
238
|
+
color: ${t(colors.blue[700], colors.blue[300])};
|
|
239
|
+
`,
|
|
201
240
|
hotkeyBadges: css`
|
|
202
241
|
display: flex;
|
|
203
242
|
gap: ${size[1]};
|
|
@@ -250,6 +289,11 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
250
289
|
background: ${t(colors.blue[100], colors.blue[900] + alpha[30])};
|
|
251
290
|
`,
|
|
252
291
|
stateDetails: css`
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-direction: column;
|
|
294
|
+
flex: 1;
|
|
295
|
+
min-height: 0;
|
|
296
|
+
overflow-y: auto;
|
|
253
297
|
padding: ${size[2]};
|
|
254
298
|
`,
|
|
255
299
|
stateHeader: css`
|
|
@@ -379,6 +423,11 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
379
423
|
text-transform: capitalize;
|
|
380
424
|
white-space: nowrap;
|
|
381
425
|
`,
|
|
426
|
+
keyCapLargeInProgress: css`
|
|
427
|
+
border-color: ${t(colors.blue[500], colors.blue[400])};
|
|
428
|
+
color: ${t(colors.blue[900], colors.blue[200])};
|
|
429
|
+
background: ${t(colors.blue[50], colors.darkGray[500])};
|
|
430
|
+
`,
|
|
382
431
|
conflictList: css`
|
|
383
432
|
display: flex;
|
|
384
433
|
flex-direction: column;
|