@tanstack/hotkeys-devtools 0.5.0 → 0.7.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/README.md +1 -1
- package/dist/components/DetailsPanel.js +338 -224
- package/dist/components/DetailsPanel.js.map +1 -1
- package/dist/components/HeldKeysTopbar.js +7 -4
- package/dist/components/HeldKeysTopbar.js.map +1 -1
- package/dist/styles/use-styles.js +60 -2
- package/dist/styles/use-styles.js.map +1 -1
- package/package.json +2 -2
- package/src/components/DetailsPanel.tsx +136 -24
- package/src/components/HeldKeysTopbar.tsx +9 -5
- package/src/styles/use-styles.ts +60 -2
|
@@ -1,17 +1,64 @@
|
|
|
1
1
|
import { For, Show, createMemo } from 'solid-js'
|
|
2
2
|
import clsx from 'clsx'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
detectPlatform,
|
|
5
|
+
formatForDisplay,
|
|
6
|
+
formatHotkeySequence,
|
|
7
|
+
normalizeHotkey,
|
|
8
|
+
normalizeHotkeyFromParsed,
|
|
9
|
+
parseHotkey,
|
|
10
|
+
} from '@tanstack/hotkeys'
|
|
4
11
|
import { useStyles } from '../styles/use-styles'
|
|
5
12
|
import { useHotkeysDevtoolsState } from '../HotkeysContextProvider'
|
|
6
13
|
import { effectiveSequenceMatchedSteps } from '../sequence-progress'
|
|
7
14
|
import { ActionButtons } from './ActionButtons'
|
|
8
15
|
import type {
|
|
9
16
|
ConflictBehavior,
|
|
17
|
+
FormatDisplayOptions,
|
|
10
18
|
HotkeyRegistration,
|
|
11
19
|
HotkeySequence,
|
|
20
|
+
ParsedHotkey,
|
|
21
|
+
RegisterableHotkey,
|
|
12
22
|
SequenceRegistrationView,
|
|
13
23
|
} from '@tanstack/hotkeys'
|
|
14
24
|
|
|
25
|
+
const PLATFORM_ROWS = [
|
|
26
|
+
{ id: 'mac' as const, label: 'macOS' },
|
|
27
|
+
{ id: 'windows' as const, label: 'Windows' },
|
|
28
|
+
{ id: 'linux' as const, label: 'Linux' },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
function KeyBreakdownPlatformTable(props: {
|
|
32
|
+
getParsed: () => ParsedHotkey
|
|
33
|
+
/** Platform used when the parsed hotkey was produced (registration `options.platform`). */
|
|
34
|
+
getCanonicalPlatform: () => 'mac' | 'windows' | 'linux'
|
|
35
|
+
styles: ReturnType<typeof useStyles>
|
|
36
|
+
}) {
|
|
37
|
+
const st = () => props.styles()
|
|
38
|
+
return (
|
|
39
|
+
<div class={st().platformBreakdown}>
|
|
40
|
+
<For each={PLATFORM_ROWS}>
|
|
41
|
+
{(row) => (
|
|
42
|
+
<div class={st().platformBreakdownRow}>
|
|
43
|
+
<span class={st().platformBreakdownLabel}>{row.label}</span>
|
|
44
|
+
<span class={st().platformBreakdownPretty}>
|
|
45
|
+
{formatForDisplay(
|
|
46
|
+
normalizeHotkeyFromParsed(
|
|
47
|
+
props.getParsed(),
|
|
48
|
+
props.getCanonicalPlatform(),
|
|
49
|
+
),
|
|
50
|
+
{
|
|
51
|
+
platform: row.id,
|
|
52
|
+
},
|
|
53
|
+
)}
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</For>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
15
62
|
function sequenceKey(sequence: Array<string>): string {
|
|
16
63
|
return sequence.join('|')
|
|
17
64
|
}
|
|
@@ -145,7 +192,10 @@ function HotkeyDetails(props: {
|
|
|
145
192
|
| 'conflictItemError'
|
|
146
193
|
| 'conflictItemScope'
|
|
147
194
|
getConflictLabel: (b: ConflictBehavior, same: boolean) => string
|
|
148
|
-
formatForDisplay: (
|
|
195
|
+
formatForDisplay: (
|
|
196
|
+
hotkey: RegisterableHotkey,
|
|
197
|
+
options?: FormatDisplayOptions,
|
|
198
|
+
) => string
|
|
149
199
|
styles: ReturnType<typeof useStyles>
|
|
150
200
|
}) {
|
|
151
201
|
const reg = () => props.registration
|
|
@@ -163,16 +213,11 @@ function HotkeyDetails(props: {
|
|
|
163
213
|
const conflictBehavior = (): ConflictBehavior =>
|
|
164
214
|
reg().options.conflictBehavior ?? 'warn'
|
|
165
215
|
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (p.alt) parts.push('Alt')
|
|
172
|
-
if (p.meta) parts.push('Meta')
|
|
173
|
-
parts.push(p.key)
|
|
174
|
-
return parts
|
|
175
|
-
})
|
|
216
|
+
const registrationPlatform = () => reg().options.platform ?? detectPlatform()
|
|
217
|
+
|
|
218
|
+
const rawModParts = createMemo(() =>
|
|
219
|
+
normalizeHotkeyFromParsed(parsed(), registrationPlatform()).split('+'),
|
|
220
|
+
)
|
|
176
221
|
|
|
177
222
|
const styles = props.styles
|
|
178
223
|
|
|
@@ -197,17 +242,31 @@ function HotkeyDetails(props: {
|
|
|
197
242
|
<div class={styles().detailsGrid}>
|
|
198
243
|
<div class={styles().detailSection}>
|
|
199
244
|
<div class={styles().detailSectionHeader}>Key Breakdown</div>
|
|
200
|
-
<div class={styles().
|
|
201
|
-
<
|
|
202
|
-
{(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
245
|
+
<div class={styles().keyBreakdownSplitRow}>
|
|
246
|
+
<div class={styles().keyBreakdownSplitLeft}>
|
|
247
|
+
<div class={styles().keyBreakdown}>
|
|
248
|
+
<For each={rawModParts()}>
|
|
249
|
+
{(part, i) => (
|
|
250
|
+
<>
|
|
251
|
+
<Show when={i() > 0}>
|
|
252
|
+
<span class={styles().keyBreakdownPlus}>+</span>
|
|
253
|
+
</Show>
|
|
254
|
+
<span class={styles().keyCapLarge}>{part}</span>
|
|
255
|
+
</>
|
|
256
|
+
)}
|
|
257
|
+
</For>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<span class={styles().keyBreakdownSplitArrow} aria-hidden="true">
|
|
261
|
+
{'->'}
|
|
262
|
+
</span>
|
|
263
|
+
<div class={styles().keyBreakdownSplitRight}>
|
|
264
|
+
<KeyBreakdownPlatformTable
|
|
265
|
+
getParsed={() => parsed()}
|
|
266
|
+
getCanonicalPlatform={registrationPlatform}
|
|
267
|
+
styles={props.styles}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
211
270
|
</div>
|
|
212
271
|
</div>
|
|
213
272
|
|
|
@@ -366,7 +425,10 @@ function SequenceDetails(props: {
|
|
|
366
425
|
| 'conflictItemScope'
|
|
367
426
|
getConflictLabel: (b: ConflictBehavior, same: boolean) => string
|
|
368
427
|
formatHotkeySequence: (seq: HotkeySequence) => string
|
|
369
|
-
formatForDisplay: (
|
|
428
|
+
formatForDisplay: (
|
|
429
|
+
hotkey: RegisterableHotkey,
|
|
430
|
+
options?: FormatDisplayOptions,
|
|
431
|
+
) => string
|
|
370
432
|
styles: ReturnType<typeof useStyles>
|
|
371
433
|
}) {
|
|
372
434
|
const reg = () => props.registration
|
|
@@ -397,6 +459,10 @@ function SequenceDetails(props: {
|
|
|
397
459
|
const conflictBehavior = (): ConflictBehavior =>
|
|
398
460
|
liveReg().options.conflictBehavior ?? 'warn'
|
|
399
461
|
|
|
462
|
+
const sequenceCanonicalPlatform = createMemo(
|
|
463
|
+
() => liveReg().options.platform ?? detectPlatform(),
|
|
464
|
+
)
|
|
465
|
+
|
|
400
466
|
const styles = props.styles
|
|
401
467
|
|
|
402
468
|
return (
|
|
@@ -471,6 +537,52 @@ function SequenceDetails(props: {
|
|
|
471
537
|
)}
|
|
472
538
|
</For>
|
|
473
539
|
</div>
|
|
540
|
+
|
|
541
|
+
<div class={styles().keyBreakdownSubHeader}>Each chord</div>
|
|
542
|
+
<For each={liveReg().sequence}>
|
|
543
|
+
{(step, stepIdx) => {
|
|
544
|
+
const stepParsed = () =>
|
|
545
|
+
parseHotkey(step, sequenceCanonicalPlatform())
|
|
546
|
+
const stepRawParts = () =>
|
|
547
|
+
normalizeHotkey(step, sequenceCanonicalPlatform()).split('+')
|
|
548
|
+
return (
|
|
549
|
+
<div class={styles().sequenceChordDetail}>
|
|
550
|
+
<div class={styles().keyBreakdownSubHeader}>
|
|
551
|
+
Step {stepIdx() + 1}
|
|
552
|
+
</div>
|
|
553
|
+
<div class={styles().keyBreakdownSplitRow}>
|
|
554
|
+
<div class={styles().keyBreakdownSplitLeft}>
|
|
555
|
+
<div class={styles().keyBreakdown}>
|
|
556
|
+
<For each={stepRawParts()}>
|
|
557
|
+
{(part, i) => (
|
|
558
|
+
<>
|
|
559
|
+
<Show when={i() > 0}>
|
|
560
|
+
<span class={styles().keyBreakdownPlus}>+</span>
|
|
561
|
+
</Show>
|
|
562
|
+
<span class={styles().keyCapLarge}>{part}</span>
|
|
563
|
+
</>
|
|
564
|
+
)}
|
|
565
|
+
</For>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
<span
|
|
569
|
+
class={styles().keyBreakdownSplitArrow}
|
|
570
|
+
aria-hidden="true"
|
|
571
|
+
>
|
|
572
|
+
{'->'}
|
|
573
|
+
</span>
|
|
574
|
+
<div class={styles().keyBreakdownSplitRight}>
|
|
575
|
+
<KeyBreakdownPlatformTable
|
|
576
|
+
getParsed={stepParsed}
|
|
577
|
+
getCanonicalPlatform={() => sequenceCanonicalPlatform()}
|
|
578
|
+
styles={props.styles}
|
|
579
|
+
/>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
)
|
|
584
|
+
}}
|
|
585
|
+
</For>
|
|
474
586
|
</div>
|
|
475
587
|
|
|
476
588
|
<div class={styles().detailSection}>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { For, Show } from 'solid-js'
|
|
2
|
-
import {
|
|
2
|
+
import { detectPlatform, formatForDisplay } from '@tanstack/hotkeys'
|
|
3
3
|
import { useStyles } from '../styles/use-styles'
|
|
4
4
|
import { useHotkeysDevtoolsState } from '../HotkeysContextProvider'
|
|
5
|
+
import type { RegisterableHotkey } from '@tanstack/hotkeys'
|
|
5
6
|
|
|
6
7
|
export function HeldKeysBar() {
|
|
7
8
|
const styles = useStyles()
|
|
8
9
|
const state = useHotkeysDevtoolsState()
|
|
10
|
+
const platform = detectPlatform()
|
|
9
11
|
|
|
10
12
|
return (
|
|
11
13
|
<div class={styles().heldKeysBar}>
|
|
@@ -18,12 +20,14 @@ export function HeldKeysBar() {
|
|
|
18
20
|
<For each={state.heldKeys()}>
|
|
19
21
|
{(key) => {
|
|
20
22
|
const code = () => state.heldCodes()[key]
|
|
21
|
-
const label = () =>
|
|
23
|
+
const label = () =>
|
|
24
|
+
formatForDisplay(key as RegisterableHotkey, {
|
|
25
|
+
platform,
|
|
26
|
+
useSymbols: true,
|
|
27
|
+
})
|
|
22
28
|
const codeLabel = () => {
|
|
23
29
|
const c = code()
|
|
24
|
-
return c
|
|
25
|
-
? formatKeyForDebuggingDisplay(c, { source: 'code' })
|
|
26
|
-
: undefined
|
|
30
|
+
return c || undefined
|
|
27
31
|
}
|
|
28
32
|
return (
|
|
29
33
|
<span class={styles().keyCap}>
|
package/src/styles/use-styles.ts
CHANGED
|
@@ -36,7 +36,7 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
36
36
|
overflow: hidden;
|
|
37
37
|
padding: ${size[2]};
|
|
38
38
|
padding-top: 0;
|
|
39
|
-
margin-top:
|
|
39
|
+
margin-top: -8px;
|
|
40
40
|
`,
|
|
41
41
|
heldKeysBar: css`
|
|
42
42
|
background: ${t(colors.gray[100], colors.darkGray[800])};
|
|
@@ -47,7 +47,7 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
47
47
|
gap: ${size[3]};
|
|
48
48
|
padding: ${size[3]} ${size[3]};
|
|
49
49
|
flex-shrink: 0;
|
|
50
|
-
min-height:
|
|
50
|
+
min-height: 72px;
|
|
51
51
|
margin-bottom: ${size[2]};
|
|
52
52
|
`,
|
|
53
53
|
heldKeysBarHeader: css`
|
|
@@ -405,6 +405,64 @@ const stylesFactory = (theme: 'light' | 'dark') => {
|
|
|
405
405
|
color: ${t(colors.gray[500], colors.gray[400])};
|
|
406
406
|
font-size: ${fontSize.sm};
|
|
407
407
|
`,
|
|
408
|
+
keyBreakdownSubHeader: css`
|
|
409
|
+
font-size: ${fontSize.xs};
|
|
410
|
+
font-weight: ${font.weight.semibold};
|
|
411
|
+
color: ${t(colors.gray[500], colors.gray[400])};
|
|
412
|
+
margin-top: ${size[3]};
|
|
413
|
+
margin-bottom: ${size[1]};
|
|
414
|
+
`,
|
|
415
|
+
keyBreakdownSplitRow: css`
|
|
416
|
+
display: flex;
|
|
417
|
+
flex-direction: row;
|
|
418
|
+
flex-wrap: wrap;
|
|
419
|
+
align-items: center;
|
|
420
|
+
gap: ${size[3]};
|
|
421
|
+
`,
|
|
422
|
+
keyBreakdownSplitLeft: css`
|
|
423
|
+
flex: 0 0 auto;
|
|
424
|
+
display: flex;
|
|
425
|
+
flex-wrap: wrap;
|
|
426
|
+
align-items: center;
|
|
427
|
+
gap: ${size[1]};
|
|
428
|
+
`,
|
|
429
|
+
keyBreakdownSplitArrow: css`
|
|
430
|
+
flex-shrink: 0;
|
|
431
|
+
color: ${t(colors.gray[500], colors.gray[400])};
|
|
432
|
+
font-size: ${fontSize.sm};
|
|
433
|
+
font-family: ${fontFamily.mono};
|
|
434
|
+
user-select: none;
|
|
435
|
+
`,
|
|
436
|
+
keyBreakdownSplitRight: css`
|
|
437
|
+
flex: 1 1 200px;
|
|
438
|
+
min-width: min(220px, 100%);
|
|
439
|
+
`,
|
|
440
|
+
platformBreakdown: css`
|
|
441
|
+
display: flex;
|
|
442
|
+
flex-direction: column;
|
|
443
|
+
gap: ${size[1]};
|
|
444
|
+
`,
|
|
445
|
+
platformBreakdownRow: css`
|
|
446
|
+
display: grid;
|
|
447
|
+
grid-template-columns: 72px 1fr;
|
|
448
|
+
gap: ${size[2]};
|
|
449
|
+
align-items: center;
|
|
450
|
+
font-size: ${fontSize.xs};
|
|
451
|
+
`,
|
|
452
|
+
platformBreakdownLabel: css`
|
|
453
|
+
color: ${t(colors.gray[500], colors.gray[400])};
|
|
454
|
+
flex-shrink: 0;
|
|
455
|
+
`,
|
|
456
|
+
platformBreakdownPretty: css`
|
|
457
|
+
color: ${t(colors.gray[900], colors.gray[100])};
|
|
458
|
+
word-break: break-word;
|
|
459
|
+
min-width: 0;
|
|
460
|
+
`,
|
|
461
|
+
sequenceChordDetail: css`
|
|
462
|
+
margin-top: ${size[3]};
|
|
463
|
+
padding-top: ${size[2]};
|
|
464
|
+
border-top: 1px solid ${t(colors.gray[200], colors.darkGray[600])};
|
|
465
|
+
`,
|
|
408
466
|
keyCapLarge: css`
|
|
409
467
|
display: inline-flex;
|
|
410
468
|
align-items: center;
|