@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.
@@ -1,17 +1,64 @@
1
1
  import { For, Show, createMemo } from 'solid-js'
2
2
  import clsx from 'clsx'
3
- import { formatForDisplay, formatHotkeySequence } from '@tanstack/hotkeys'
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: (hotkey: string) => string
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 keyParts = createMemo(() => {
167
- const parts: Array<string> = []
168
- const p = parsed()
169
- if (p.ctrl) parts.push('Ctrl')
170
- if (p.shift) parts.push('Shift')
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().keyBreakdown}>
201
- <For each={keyParts()}>
202
- {(part, i) => (
203
- <>
204
- <Show when={i() > 0}>
205
- <span class={styles().keyBreakdownPlus}>+</span>
206
- </Show>
207
- <span class={styles().keyCapLarge}>{part}</span>
208
- </>
209
- )}
210
- </For>
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: (hotkey: string) => string
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 { formatKeyForDebuggingDisplay } from '@tanstack/hotkeys'
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 = () => formatKeyForDebuggingDisplay(key)
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}>
@@ -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: ${size[2]};
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: 64px;
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;