@tanstack/hotkeys-devtools 0.8.1 → 1.0.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 +15 -1
- package/dist/_registrations.js +48 -0
- package/dist/_registrations.js.map +1 -0
- package/dist/components/DetailsPanel.js +302 -323
- package/dist/components/DetailsPanel.js.map +1 -1
- package/dist/components/HotkeyList.js +155 -194
- package/dist/components/HotkeyList.js.map +1 -1
- package/package.json +4 -4
- package/src/_registrations.ts +59 -0
- package/src/components/DetailsPanel.tsx +53 -197
- package/src/components/HotkeyList.tsx +48 -140
|
@@ -3,22 +3,22 @@ import clsx from 'clsx'
|
|
|
3
3
|
import {
|
|
4
4
|
detectPlatform,
|
|
5
5
|
formatForDisplay,
|
|
6
|
-
formatHotkeySequence,
|
|
7
|
-
normalizeHotkey,
|
|
8
6
|
normalizeHotkeyFromParsed,
|
|
9
7
|
parseHotkey,
|
|
10
8
|
} from '@tanstack/hotkeys'
|
|
11
9
|
import { useStyles } from '../styles/use-styles'
|
|
12
10
|
import { useHotkeysDevtoolsState } from '../HotkeysContextProvider'
|
|
13
11
|
import { effectiveSequenceMatchedSteps } from '../sequence-progress'
|
|
12
|
+
import {
|
|
13
|
+
formatRegistration,
|
|
14
|
+
getBindingKind,
|
|
15
|
+
useRegistrationConflicts,
|
|
16
|
+
} from '../_registrations'
|
|
14
17
|
import { ActionButtons } from './ActionButtons'
|
|
15
18
|
import type {
|
|
16
19
|
ConflictBehavior,
|
|
17
|
-
FormatDisplayOptions,
|
|
18
20
|
HotkeyRegistration,
|
|
19
|
-
HotkeySequence,
|
|
20
21
|
ParsedHotkey,
|
|
21
|
-
RegisterableHotkey,
|
|
22
22
|
SequenceRegistrationView,
|
|
23
23
|
} from '@tanstack/hotkeys'
|
|
24
24
|
|
|
@@ -59,10 +59,6 @@ function KeyBreakdownPlatformTable(props: {
|
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function sequenceKey(sequence: Array<string>): string {
|
|
63
|
-
return sequence.join('|')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
62
|
function isSequenceRegistration(
|
|
67
63
|
reg: HotkeyRegistration | SequenceRegistrationView,
|
|
68
64
|
): reg is SequenceRegistrationView {
|
|
@@ -94,129 +90,27 @@ function getTargetDescription(target: HTMLElement | Document | Window): string {
|
|
|
94
90
|
return 'element'
|
|
95
91
|
}
|
|
96
92
|
|
|
97
|
-
function findTargetConflicts(
|
|
98
|
-
registration: HotkeyRegistration,
|
|
99
|
-
all: Array<HotkeyRegistration>,
|
|
100
|
-
): Array<HotkeyRegistration> {
|
|
101
|
-
return all.filter(
|
|
102
|
-
(other) =>
|
|
103
|
-
other.id !== registration.id &&
|
|
104
|
-
other.hotkey === registration.hotkey &&
|
|
105
|
-
other.options.eventType === registration.options.eventType &&
|
|
106
|
-
other.target === registration.target,
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function findScopeConflicts(
|
|
111
|
-
registration: HotkeyRegistration,
|
|
112
|
-
all: Array<HotkeyRegistration>,
|
|
113
|
-
): Array<HotkeyRegistration> {
|
|
114
|
-
return all.filter(
|
|
115
|
-
(other) =>
|
|
116
|
-
other.id !== registration.id &&
|
|
117
|
-
other.hotkey === registration.hotkey &&
|
|
118
|
-
other.options.eventType === registration.options.eventType &&
|
|
119
|
-
other.target !== registration.target,
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function findSequenceTargetConflicts(
|
|
124
|
-
registration: SequenceRegistrationView,
|
|
125
|
-
all: Array<SequenceRegistrationView>,
|
|
126
|
-
): Array<SequenceRegistrationView> {
|
|
127
|
-
return all.filter(
|
|
128
|
-
(other) =>
|
|
129
|
-
other.id !== registration.id &&
|
|
130
|
-
sequenceKey(other.sequence) === sequenceKey(registration.sequence) &&
|
|
131
|
-
other.options.eventType === registration.options.eventType &&
|
|
132
|
-
other.target === registration.target,
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function findSequenceScopeConflicts(
|
|
137
|
-
registration: SequenceRegistrationView,
|
|
138
|
-
all: Array<SequenceRegistrationView>,
|
|
139
|
-
): Array<SequenceRegistrationView> {
|
|
140
|
-
return all.filter(
|
|
141
|
-
(other) =>
|
|
142
|
-
other.id !== registration.id &&
|
|
143
|
-
sequenceKey(other.sequence) === sequenceKey(registration.sequence) &&
|
|
144
|
-
other.options.eventType === registration.options.eventType &&
|
|
145
|
-
other.target !== registration.target,
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function getConflictItemStyle(
|
|
150
|
-
behavior: ConflictBehavior,
|
|
151
|
-
isSameTarget: boolean,
|
|
152
|
-
):
|
|
153
|
-
| 'conflictItem'
|
|
154
|
-
| 'conflictItemAllow'
|
|
155
|
-
| 'conflictItemError'
|
|
156
|
-
| 'conflictItemScope' {
|
|
157
|
-
if (!isSameTarget) return 'conflictItemScope'
|
|
158
|
-
if (behavior === 'allow') return 'conflictItemAllow'
|
|
159
|
-
if (behavior === 'error') return 'conflictItemError'
|
|
160
|
-
return 'conflictItem'
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function getConflictLabel(
|
|
164
|
-
behavior: ConflictBehavior,
|
|
165
|
-
isSameTarget: boolean,
|
|
166
|
-
): string {
|
|
167
|
-
if (!isSameTarget) return 'scope'
|
|
168
|
-
if (behavior === 'allow') return 'allowed'
|
|
169
|
-
if (behavior === 'error') return 'error'
|
|
170
|
-
if (behavior === 'replace') return 'replaced'
|
|
171
|
-
return 'warning'
|
|
172
|
-
}
|
|
173
|
-
|
|
174
93
|
function HotkeyDetails(props: {
|
|
175
94
|
registration: HotkeyRegistration
|
|
176
|
-
state: ReturnType<typeof useHotkeysDevtoolsState>
|
|
177
95
|
getTargetDescription: (t: HTMLElement | Document | Window) => string
|
|
178
|
-
findTargetConflicts: (
|
|
179
|
-
r: HotkeyRegistration,
|
|
180
|
-
all: Array<HotkeyRegistration>,
|
|
181
|
-
) => Array<HotkeyRegistration>
|
|
182
|
-
findScopeConflicts: (
|
|
183
|
-
r: HotkeyRegistration,
|
|
184
|
-
all: Array<HotkeyRegistration>,
|
|
185
|
-
) => Array<HotkeyRegistration>
|
|
186
|
-
getConflictItemStyle: (
|
|
187
|
-
b: ConflictBehavior,
|
|
188
|
-
same: boolean,
|
|
189
|
-
) =>
|
|
190
|
-
| 'conflictItem'
|
|
191
|
-
| 'conflictItemAllow'
|
|
192
|
-
| 'conflictItemError'
|
|
193
|
-
| 'conflictItemScope'
|
|
194
|
-
getConflictLabel: (b: ConflictBehavior, same: boolean) => string
|
|
195
|
-
formatForDisplay: (
|
|
196
|
-
hotkey: RegisterableHotkey,
|
|
197
|
-
options?: FormatDisplayOptions,
|
|
198
|
-
) => string
|
|
199
96
|
styles: ReturnType<typeof useStyles>
|
|
200
97
|
}) {
|
|
201
98
|
const reg = () => props.registration
|
|
202
99
|
const parsed = () => reg().parsedHotkey
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
const
|
|
207
|
-
props.findScopeConflicts(reg(), props.state.registrations()),
|
|
208
|
-
)
|
|
209
|
-
const allConflicts = createMemo(() => [
|
|
210
|
-
...targetConflicts(),
|
|
211
|
-
...scopeConflicts(),
|
|
212
|
-
])
|
|
100
|
+
const conflicts = useRegistrationConflicts(reg)
|
|
101
|
+
const targetConflicts = () => conflicts().overlapping
|
|
102
|
+
const scopeConflicts = () => conflicts().separate
|
|
103
|
+
const allConflicts = () => [...targetConflicts(), ...scopeConflicts()]
|
|
213
104
|
const conflictBehavior = (): ConflictBehavior =>
|
|
214
105
|
reg().options.conflictBehavior ?? 'warn'
|
|
215
106
|
|
|
216
107
|
const registrationPlatform = () => reg().options.platform ?? detectPlatform()
|
|
217
108
|
|
|
218
|
-
const
|
|
219
|
-
|
|
109
|
+
const keyParts = createMemo(() =>
|
|
110
|
+
formatForDisplay(parsed(), {
|
|
111
|
+
platform: registrationPlatform(),
|
|
112
|
+
parts: true,
|
|
113
|
+
}),
|
|
220
114
|
)
|
|
221
115
|
|
|
222
116
|
const styles = props.styles
|
|
@@ -224,12 +118,12 @@ function HotkeyDetails(props: {
|
|
|
224
118
|
return (
|
|
225
119
|
<>
|
|
226
120
|
<div class={styles().stateHeader}>
|
|
227
|
-
<div class={styles().stateTitle}>
|
|
228
|
-
{props.formatForDisplay(reg().hotkey)}
|
|
229
|
-
</div>
|
|
121
|
+
<div class={styles().stateTitle}>{formatRegistration(reg())}</div>
|
|
230
122
|
<div class={styles().infoGrid}>
|
|
231
123
|
<div class={styles().infoLabel}>ID</div>
|
|
232
124
|
<div class={styles().infoValueMono}>{reg().id}</div>
|
|
125
|
+
<div class={styles().infoLabel}>Matches</div>
|
|
126
|
+
<div class={styles().infoValueMono}>{getBindingKind(reg())}</div>
|
|
233
127
|
<div class={styles().infoLabel}>Raw</div>
|
|
234
128
|
<div class={styles().infoValueMono}>{reg().hotkey}</div>
|
|
235
129
|
<div class={styles().infoLabel}>Target</div>
|
|
@@ -245,7 +139,7 @@ function HotkeyDetails(props: {
|
|
|
245
139
|
<div class={styles().keyBreakdownSplitRow}>
|
|
246
140
|
<div class={styles().keyBreakdownSplitLeft}>
|
|
247
141
|
<div class={styles().keyBreakdown}>
|
|
248
|
-
<For each={
|
|
142
|
+
<For each={keyParts()}>
|
|
249
143
|
{(part, i) => (
|
|
250
144
|
<>
|
|
251
145
|
<Show when={i() > 0}>
|
|
@@ -410,14 +304,10 @@ function HotkeyDetails(props: {
|
|
|
410
304
|
<div class={styles().conflictList}>
|
|
411
305
|
<For each={targetConflicts()}>
|
|
412
306
|
{(conflict) => {
|
|
413
|
-
const itemStyle = () =>
|
|
414
|
-
props.getConflictItemStyle(conflictBehavior(), true)
|
|
415
|
-
const label = () =>
|
|
416
|
-
props.getConflictLabel(conflictBehavior(), true)
|
|
417
307
|
return (
|
|
418
|
-
<div class={styles()
|
|
419
|
-
<span>
|
|
420
|
-
{
|
|
308
|
+
<div class={styles().conflictItem}>
|
|
309
|
+
<span>overlap</span> {conflict.id}:{' '}
|
|
310
|
+
{formatRegistration(conflict)} (
|
|
421
311
|
{conflict.options.eventType ?? 'keydown'}) on{' '}
|
|
422
312
|
{props.getTargetDescription(conflict.target)}
|
|
423
313
|
</div>
|
|
@@ -428,7 +318,7 @@ function HotkeyDetails(props: {
|
|
|
428
318
|
{(conflict) => (
|
|
429
319
|
<div class={styles().conflictItemScope}>
|
|
430
320
|
<span>scope</span> {conflict.id}:{' '}
|
|
431
|
-
{
|
|
321
|
+
{formatRegistration(conflict)} (
|
|
432
322
|
{conflict.options.eventType ?? 'keydown'}) on{' '}
|
|
433
323
|
{props.getTargetDescription(conflict.target)}
|
|
434
324
|
</div>
|
|
@@ -446,28 +336,6 @@ function SequenceDetails(props: {
|
|
|
446
336
|
registration: SequenceRegistrationView
|
|
447
337
|
state: ReturnType<typeof useHotkeysDevtoolsState>
|
|
448
338
|
getTargetDescription: (t: HTMLElement | Document | Window) => string
|
|
449
|
-
findSequenceTargetConflicts: (
|
|
450
|
-
r: SequenceRegistrationView,
|
|
451
|
-
all: Array<SequenceRegistrationView>,
|
|
452
|
-
) => Array<SequenceRegistrationView>
|
|
453
|
-
findSequenceScopeConflicts: (
|
|
454
|
-
r: SequenceRegistrationView,
|
|
455
|
-
all: Array<SequenceRegistrationView>,
|
|
456
|
-
) => Array<SequenceRegistrationView>
|
|
457
|
-
getConflictItemStyle: (
|
|
458
|
-
b: ConflictBehavior,
|
|
459
|
-
same: boolean,
|
|
460
|
-
) =>
|
|
461
|
-
| 'conflictItem'
|
|
462
|
-
| 'conflictItemAllow'
|
|
463
|
-
| 'conflictItemError'
|
|
464
|
-
| 'conflictItemScope'
|
|
465
|
-
getConflictLabel: (b: ConflictBehavior, same: boolean) => string
|
|
466
|
-
formatHotkeySequence: (seq: HotkeySequence) => string
|
|
467
|
-
formatForDisplay: (
|
|
468
|
-
hotkey: RegisterableHotkey,
|
|
469
|
-
options?: FormatDisplayOptions,
|
|
470
|
-
) => string
|
|
471
339
|
styles: ReturnType<typeof useStyles>
|
|
472
340
|
}) {
|
|
473
341
|
const reg = () => props.registration
|
|
@@ -479,22 +347,10 @@ function SequenceDetails(props: {
|
|
|
479
347
|
const matchedSteps = createMemo(() =>
|
|
480
348
|
effectiveSequenceMatchedSteps(liveReg(), props.state.sequenceProgressNow()),
|
|
481
349
|
)
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
),
|
|
487
|
-
)
|
|
488
|
-
const scopeConflicts = createMemo(() =>
|
|
489
|
-
props.findSequenceScopeConflicts(
|
|
490
|
-
liveReg(),
|
|
491
|
-
props.state.sequenceRegistrations(),
|
|
492
|
-
),
|
|
493
|
-
)
|
|
494
|
-
const allConflicts = createMemo(() => [
|
|
495
|
-
...targetConflicts(),
|
|
496
|
-
...scopeConflicts(),
|
|
497
|
-
])
|
|
350
|
+
const conflicts = useRegistrationConflicts(liveReg)
|
|
351
|
+
const targetConflicts = () => conflicts().overlapping
|
|
352
|
+
const scopeConflicts = () => conflicts().separate
|
|
353
|
+
const allConflicts = () => [...targetConflicts(), ...scopeConflicts()]
|
|
498
354
|
const conflictBehavior = (): ConflictBehavior =>
|
|
499
355
|
liveReg().options.conflictBehavior ?? 'warn'
|
|
500
356
|
|
|
@@ -519,7 +375,9 @@ function SequenceDetails(props: {
|
|
|
519
375
|
: undefined
|
|
520
376
|
}
|
|
521
377
|
>
|
|
522
|
-
{
|
|
378
|
+
{formatForDisplay(step, {
|
|
379
|
+
platform: sequenceCanonicalPlatform(),
|
|
380
|
+
})}
|
|
523
381
|
</span>
|
|
524
382
|
</span>
|
|
525
383
|
)}
|
|
@@ -528,6 +386,12 @@ function SequenceDetails(props: {
|
|
|
528
386
|
<div class={styles().infoGrid}>
|
|
529
387
|
<div class={styles().infoLabel}>ID</div>
|
|
530
388
|
<div class={styles().infoValueMono}>{liveReg().id}</div>
|
|
389
|
+
<div class={styles().infoLabel}>Matches</div>
|
|
390
|
+
<div class={styles().infoValueMono}>{getBindingKind(liveReg())}</div>
|
|
391
|
+
<div class={styles().infoLabel}>Raw</div>
|
|
392
|
+
<div class={styles().infoValueMono}>
|
|
393
|
+
{liveReg().sequence.join(' → ')}
|
|
394
|
+
</div>
|
|
531
395
|
<div class={styles().infoLabel}>Sequence</div>
|
|
532
396
|
<div class={styles().infoValueMono}>
|
|
533
397
|
<For each={liveReg().sequence}>
|
|
@@ -541,7 +405,9 @@ function SequenceDetails(props: {
|
|
|
541
405
|
: undefined
|
|
542
406
|
}
|
|
543
407
|
>
|
|
544
|
-
{
|
|
408
|
+
{formatForDisplay(step, {
|
|
409
|
+
platform: sequenceCanonicalPlatform(),
|
|
410
|
+
})}
|
|
545
411
|
</span>
|
|
546
412
|
</span>
|
|
547
413
|
)}
|
|
@@ -570,7 +436,9 @@ function SequenceDetails(props: {
|
|
|
570
436
|
i() < matchedSteps() && styles().keyCapLargeInProgress,
|
|
571
437
|
)}
|
|
572
438
|
>
|
|
573
|
-
{
|
|
439
|
+
{formatForDisplay(step, {
|
|
440
|
+
platform: sequenceCanonicalPlatform(),
|
|
441
|
+
})}
|
|
574
442
|
</span>
|
|
575
443
|
</>
|
|
576
444
|
)}
|
|
@@ -582,17 +450,21 @@ function SequenceDetails(props: {
|
|
|
582
450
|
{(step, stepIdx) => {
|
|
583
451
|
const stepParsed = () =>
|
|
584
452
|
parseHotkey(step, sequenceCanonicalPlatform())
|
|
585
|
-
const
|
|
586
|
-
|
|
453
|
+
const stepParts = () =>
|
|
454
|
+
formatForDisplay(stepParsed(), {
|
|
455
|
+
platform: sequenceCanonicalPlatform(),
|
|
456
|
+
parts: true,
|
|
457
|
+
})
|
|
587
458
|
return (
|
|
588
459
|
<div class={styles().sequenceChordDetail}>
|
|
589
460
|
<div class={styles().keyBreakdownSubHeader}>
|
|
590
|
-
Step {stepIdx() + 1}
|
|
461
|
+
Step {stepIdx() + 1} —{' '}
|
|
462
|
+
{stepParsed().code !== undefined ? 'Code' : 'Key'}
|
|
591
463
|
</div>
|
|
592
464
|
<div class={styles().keyBreakdownSplitRow}>
|
|
593
465
|
<div class={styles().keyBreakdownSplitLeft}>
|
|
594
466
|
<div class={styles().keyBreakdown}>
|
|
595
|
-
<For each={
|
|
467
|
+
<For each={stepParts()}>
|
|
596
468
|
{(part, i) => (
|
|
597
469
|
<>
|
|
598
470
|
<Show when={i() > 0}>
|
|
@@ -746,14 +618,10 @@ function SequenceDetails(props: {
|
|
|
746
618
|
<div class={styles().conflictList}>
|
|
747
619
|
<For each={targetConflicts()}>
|
|
748
620
|
{(conflict) => {
|
|
749
|
-
const itemStyle = () =>
|
|
750
|
-
props.getConflictItemStyle(conflictBehavior(), true)
|
|
751
|
-
const label = () =>
|
|
752
|
-
props.getConflictLabel(conflictBehavior(), true)
|
|
753
621
|
return (
|
|
754
|
-
<div class={styles()
|
|
755
|
-
<span>
|
|
756
|
-
{
|
|
622
|
+
<div class={styles().conflictItem}>
|
|
623
|
+
<span>overlap</span> {conflict.id}:{' '}
|
|
624
|
+
{formatRegistration(conflict)} (
|
|
757
625
|
{conflict.options.eventType ?? 'keydown'}) on{' '}
|
|
758
626
|
{props.getTargetDescription(conflict.target)}
|
|
759
627
|
</div>
|
|
@@ -764,7 +632,7 @@ function SequenceDetails(props: {
|
|
|
764
632
|
{(conflict) => (
|
|
765
633
|
<div class={styles().conflictItemScope}>
|
|
766
634
|
<span>scope</span> {conflict.id}:{' '}
|
|
767
|
-
{
|
|
635
|
+
{formatRegistration(conflict)} (
|
|
768
636
|
{conflict.options.eventType ?? 'keydown'}) on{' '}
|
|
769
637
|
{props.getTargetDescription(conflict.target)}
|
|
770
638
|
</div>
|
|
@@ -798,13 +666,7 @@ export function DetailsPanel(props: DetailsPanelProps) {
|
|
|
798
666
|
fallback={
|
|
799
667
|
<HotkeyDetails
|
|
800
668
|
registration={reg() as HotkeyRegistration}
|
|
801
|
-
state={state}
|
|
802
669
|
getTargetDescription={getTargetDescription}
|
|
803
|
-
findTargetConflicts={findTargetConflicts}
|
|
804
|
-
findScopeConflicts={findScopeConflicts}
|
|
805
|
-
getConflictItemStyle={getConflictItemStyle}
|
|
806
|
-
getConflictLabel={getConflictLabel}
|
|
807
|
-
formatForDisplay={formatForDisplay}
|
|
808
670
|
styles={styles}
|
|
809
671
|
/>
|
|
810
672
|
}
|
|
@@ -813,12 +675,6 @@ export function DetailsPanel(props: DetailsPanelProps) {
|
|
|
813
675
|
registration={reg() as SequenceRegistrationView}
|
|
814
676
|
state={state}
|
|
815
677
|
getTargetDescription={getTargetDescription}
|
|
816
|
-
findSequenceTargetConflicts={findSequenceTargetConflicts}
|
|
817
|
-
findSequenceScopeConflicts={findSequenceScopeConflicts}
|
|
818
|
-
getConflictItemStyle={getConflictItemStyle}
|
|
819
|
-
getConflictLabel={getConflictLabel}
|
|
820
|
-
formatHotkeySequence={formatHotkeySequence}
|
|
821
|
-
formatForDisplay={formatForDisplay}
|
|
822
678
|
styles={styles}
|
|
823
679
|
/>
|
|
824
680
|
</Show>
|
|
@@ -5,15 +5,12 @@ import { formatForDisplay } from '@tanstack/hotkeys'
|
|
|
5
5
|
import { useStyles } from '../styles/use-styles'
|
|
6
6
|
import { useHotkeysDevtoolsState } from '../HotkeysContextProvider'
|
|
7
7
|
import { effectiveSequenceMatchedSteps } from '../sequence-progress'
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from '
|
|
13
|
-
|
|
14
|
-
function sequenceKey(sequence: Array<string>): string {
|
|
15
|
-
return sequence.join('|')
|
|
16
|
-
}
|
|
8
|
+
import {
|
|
9
|
+
formatRegistration,
|
|
10
|
+
getBindingKind,
|
|
11
|
+
useRegistrationConflicts,
|
|
12
|
+
} from '../_registrations'
|
|
13
|
+
import type { SequenceRegistrationView } from '@tanstack/hotkeys'
|
|
17
14
|
|
|
18
15
|
type HotkeyListProps = {
|
|
19
16
|
selectedId: () => string | null
|
|
@@ -62,64 +59,14 @@ function getTargetTooltip(target: HTMLElement | Document | Window): string {
|
|
|
62
59
|
return 'Listening on element'
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
function findTargetConflicts(
|
|
66
|
-
registration: HotkeyRegistration,
|
|
67
|
-
all: Array<HotkeyRegistration>,
|
|
68
|
-
): Array<HotkeyRegistration> {
|
|
69
|
-
return all.filter(
|
|
70
|
-
(other) =>
|
|
71
|
-
other.id !== registration.id &&
|
|
72
|
-
other.hotkey === registration.hotkey &&
|
|
73
|
-
other.options.eventType === registration.options.eventType &&
|
|
74
|
-
other.target === registration.target,
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function findScopeConflicts(
|
|
79
|
-
registration: HotkeyRegistration,
|
|
80
|
-
all: Array<HotkeyRegistration>,
|
|
81
|
-
): Array<HotkeyRegistration> {
|
|
82
|
-
return all.filter(
|
|
83
|
-
(other) =>
|
|
84
|
-
other.id !== registration.id &&
|
|
85
|
-
other.hotkey === registration.hotkey &&
|
|
86
|
-
other.options.eventType === registration.options.eventType &&
|
|
87
|
-
other.target !== registration.target,
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function findSequenceTargetConflicts(
|
|
92
|
-
registration: SequenceRegistrationView,
|
|
93
|
-
all: Array<SequenceRegistrationView>,
|
|
94
|
-
): Array<SequenceRegistrationView> {
|
|
95
|
-
return all.filter(
|
|
96
|
-
(other) =>
|
|
97
|
-
other.id !== registration.id &&
|
|
98
|
-
sequenceKey(other.sequence) === sequenceKey(registration.sequence) &&
|
|
99
|
-
other.options.eventType === registration.options.eventType &&
|
|
100
|
-
other.target === registration.target,
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function findSequenceScopeConflicts(
|
|
105
|
-
registration: SequenceRegistrationView,
|
|
106
|
-
all: Array<SequenceRegistrationView>,
|
|
107
|
-
): Array<SequenceRegistrationView> {
|
|
108
|
-
return all.filter(
|
|
109
|
-
(other) =>
|
|
110
|
-
other.id !== registration.id &&
|
|
111
|
-
sequenceKey(other.sequence) === sequenceKey(registration.sequence) &&
|
|
112
|
-
other.options.eventType === registration.options.eventType &&
|
|
113
|
-
other.target !== registration.target,
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
62
|
function buildRowTooltip(meta?: {
|
|
118
63
|
name?: string
|
|
119
64
|
description?: string
|
|
65
|
+
group?: string
|
|
120
66
|
}): string | undefined {
|
|
121
67
|
if (!meta) return undefined
|
|
122
68
|
const parts: Array<string> = []
|
|
69
|
+
if (meta.group) parts.push(meta.group)
|
|
123
70
|
if (meta.name) parts.push(meta.name)
|
|
124
71
|
if (meta.description) parts.push(meta.description)
|
|
125
72
|
return parts.length > 0 ? parts.join(' — ') : undefined
|
|
@@ -136,45 +83,18 @@ function SequenceListRow(props: {
|
|
|
136
83
|
|
|
137
84
|
const sequenceRegistrations = () => props.getSequences()
|
|
138
85
|
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const scopeConflicts = () =>
|
|
142
|
-
findSequenceScopeConflicts(props.reg, sequenceRegistrations())
|
|
143
|
-
|
|
86
|
+
const conflicts = useRegistrationConflicts(() => props.reg)
|
|
87
|
+
const targetConflicts = () => conflicts().overlapping
|
|
88
|
+
const scopeConflicts = () => conflicts().separate
|
|
144
89
|
const hasTargetConflict = () => targetConflicts().length > 0
|
|
145
90
|
const hasScopeConflict = () => scopeConflicts().length > 0
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (behavior === 'allow') {
|
|
154
|
-
return {
|
|
155
|
-
style: 'badgeAllow' as const,
|
|
156
|
-
label: '~',
|
|
157
|
-
tooltip: `Allowed: ${c.length} other binding${c.length > 1 ? 's' : ''} on same sequence and target (conflictBehavior: allow)`,
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (behavior === 'error') {
|
|
161
|
-
return {
|
|
162
|
-
style: 'badgeError' as const,
|
|
163
|
-
label: '!',
|
|
164
|
-
tooltip: `Error: ${c.length} conflicting binding${c.length > 1 ? 's' : ''} on same sequence and target (conflictBehavior: error)`,
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
style: 'badgeConflict' as const,
|
|
169
|
-
label: '!',
|
|
170
|
-
tooltip: `Warning: ${c.length} other binding${c.length > 1 ? 's' : ''} on same sequence and target`,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const scopeConflictTooltip = () => {
|
|
175
|
-
const c = scopeConflicts()
|
|
176
|
-
return `Info: ${c.length} binding${c.length > 1 ? 's' : ''} with same sequence on different target${c.length > 1 ? 's' : ''}`
|
|
177
|
-
}
|
|
91
|
+
const targetConflictBadge = () => ({
|
|
92
|
+
style: 'badgeConflict' as const,
|
|
93
|
+
label: '!',
|
|
94
|
+
tooltip: `Potential overlap: ${targetConflicts().length} other bindings on overlapping targets`,
|
|
95
|
+
})
|
|
96
|
+
const scopeConflictTooltip = () =>
|
|
97
|
+
`Info: ${scopeConflicts().length} related bindings on separate targets`
|
|
178
98
|
|
|
179
99
|
const enabled = () => props.reg.options.enabled !== false
|
|
180
100
|
|
|
@@ -238,7 +158,9 @@ function SequenceListRow(props: {
|
|
|
238
158
|
: undefined
|
|
239
159
|
}
|
|
240
160
|
>
|
|
241
|
-
{formatForDisplay(step
|
|
161
|
+
{formatForDisplay(step, {
|
|
162
|
+
platform: props.reg.options.platform,
|
|
163
|
+
})}
|
|
242
164
|
</span>
|
|
243
165
|
</span>
|
|
244
166
|
)}
|
|
@@ -252,6 +174,12 @@ function SequenceListRow(props: {
|
|
|
252
174
|
<span class={styles().triggerCount}>x{triggerCount()}</span>
|
|
253
175
|
</Show>
|
|
254
176
|
<div class={styles().hotkeyBadges}>
|
|
177
|
+
<span
|
|
178
|
+
class={clsx(styles().badge, styles().badgeTarget)}
|
|
179
|
+
data-binding-kind
|
|
180
|
+
>
|
|
181
|
+
{getBindingKind(props.reg)}
|
|
182
|
+
</span>
|
|
255
183
|
{hasTargetConflict() && (
|
|
256
184
|
<span
|
|
257
185
|
class={clsx(
|
|
@@ -329,10 +257,11 @@ export function HotkeyList(props: HotkeyListProps) {
|
|
|
329
257
|
if (!query) return registrations()
|
|
330
258
|
return matchSorter(registrations(), query, {
|
|
331
259
|
keys: [
|
|
332
|
-
(r) =>
|
|
260
|
+
(r) => formatRegistration(r),
|
|
333
261
|
'hotkey',
|
|
334
262
|
'options.meta.name',
|
|
335
263
|
'options.meta.description',
|
|
264
|
+
'options.meta.group',
|
|
336
265
|
],
|
|
337
266
|
})
|
|
338
267
|
})
|
|
@@ -342,10 +271,11 @@ export function HotkeyList(props: HotkeyListProps) {
|
|
|
342
271
|
if (!query) return sequenceRegistrations()
|
|
343
272
|
return matchSorter(sequenceRegistrations(), query, {
|
|
344
273
|
keys: [
|
|
345
|
-
(r) => r
|
|
274
|
+
(r) => formatRegistration(r),
|
|
346
275
|
(r) => r.sequence.join(' '),
|
|
347
276
|
'options.meta.name',
|
|
348
277
|
'options.meta.description',
|
|
278
|
+
'options.meta.group',
|
|
349
279
|
],
|
|
350
280
|
})
|
|
351
281
|
})
|
|
@@ -376,46 +306,18 @@ export function HotkeyList(props: HotkeyListProps) {
|
|
|
376
306
|
<div class={styles().hotkeyList}>
|
|
377
307
|
<For each={filteredRegistrations()}>
|
|
378
308
|
{(reg) => {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
const scopeConflicts = () =>
|
|
382
|
-
findScopeConflicts(reg, registrations())
|
|
383
|
-
|
|
309
|
+
const conflicts = useRegistrationConflicts(() => reg)
|
|
310
|
+
const targetConflicts = () => conflicts().overlapping
|
|
311
|
+
const scopeConflicts = () => conflicts().separate
|
|
384
312
|
const hasTargetConflict = () => targetConflicts().length > 0
|
|
385
313
|
const hasScopeConflict = () => scopeConflicts().length > 0
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (behavior === 'allow') {
|
|
394
|
-
return {
|
|
395
|
-
style: 'badgeAllow' as const,
|
|
396
|
-
label: '~',
|
|
397
|
-
tooltip: `Allowed: ${c.length} other binding${c.length > 1 ? 's' : ''} on same key and target (conflictBehavior: allow)`,
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (behavior === 'error') {
|
|
401
|
-
return {
|
|
402
|
-
style: 'badgeError' as const,
|
|
403
|
-
label: '!',
|
|
404
|
-
tooltip: `Error: ${c.length} conflicting binding${c.length > 1 ? 's' : ''} on same key and target (conflictBehavior: error)`,
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
// 'warn' (default) or 'replace' (replacement already happened, but show warn-style if somehow present)
|
|
408
|
-
return {
|
|
409
|
-
style: 'badgeConflict' as const,
|
|
410
|
-
label: '!',
|
|
411
|
-
tooltip: `Warning: ${c.length} other binding${c.length > 1 ? 's' : ''} on same key and target`,
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const scopeConflictTooltip = () => {
|
|
416
|
-
const c = scopeConflicts()
|
|
417
|
-
return `Info: ${c.length} binding${c.length > 1 ? 's' : ''} with same key on different target${c.length > 1 ? 's' : ''}`
|
|
418
|
-
}
|
|
314
|
+
const targetConflictBadge = () => ({
|
|
315
|
+
style: 'badgeConflict' as const,
|
|
316
|
+
label: '!',
|
|
317
|
+
tooltip: `Potential overlap: ${targetConflicts().length} other bindings on overlapping targets`,
|
|
318
|
+
})
|
|
319
|
+
const scopeConflictTooltip = () =>
|
|
320
|
+
`Info: ${scopeConflicts().length} related bindings on separate targets`
|
|
419
321
|
|
|
420
322
|
const enabled = () => reg.options.enabled !== false
|
|
421
323
|
|
|
@@ -459,7 +361,7 @@ export function HotkeyList(props: HotkeyListProps) {
|
|
|
459
361
|
</Show>
|
|
460
362
|
<span class={styles().hotkeyLabel}>
|
|
461
363
|
<span class={styles().hotkeyLabelKeys}>
|
|
462
|
-
{
|
|
364
|
+
{formatRegistration(reg)}
|
|
463
365
|
</span>
|
|
464
366
|
<Show when={metaName()}>
|
|
465
367
|
<span class={styles().hotkeyLabelName}>{metaName()}</span>
|
|
@@ -469,6 +371,12 @@ export function HotkeyList(props: HotkeyListProps) {
|
|
|
469
371
|
<span class={styles().triggerCount}>x{triggerCount()}</span>
|
|
470
372
|
</Show>
|
|
471
373
|
<div class={styles().hotkeyBadges}>
|
|
374
|
+
<span
|
|
375
|
+
class={clsx(styles().badge, styles().badgeTarget)}
|
|
376
|
+
data-binding-kind
|
|
377
|
+
>
|
|
378
|
+
{getBindingKind(reg)}
|
|
379
|
+
</span>
|
|
472
380
|
{hasTargetConflict() && (
|
|
473
381
|
<span
|
|
474
382
|
class={clsx(
|