Skip to content

Commit 2d47d40

Browse files
committed
chore(vault): small improvements
1 parent 213ed01 commit 2d47d40

File tree

7 files changed

+196
-27
lines changed

7 files changed

+196
-27
lines changed

services/vault/src/components/deposit/MnemonicModal/ImportForm.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Button, Text } from "@babylonlabs-io/core-ui";
22
import { useCallback, useRef, useState } from "react";
33

4+
import { getNextFocusIndex } from "@/utils/wordGridKeyboardNav";
5+
46
const WORD_COUNT = 12;
57

68
interface ImportFormProps {
@@ -48,22 +50,14 @@ export function ImportForm({
4850

4951
const handleKeyDown = useCallback(
5052
(index: number, e: React.KeyboardEvent<HTMLInputElement>) => {
51-
if (e.key === " ") {
52-
e.preventDefault();
53-
if (index < WORD_COUNT - 1) {
54-
inputRefs.current[index + 1]?.focus();
55-
}
56-
} else if (e.key === "Tab") {
57-
if (e.shiftKey && index > 0) {
58-
e.preventDefault();
59-
inputRefs.current[index - 1]?.focus();
60-
} else if (!e.shiftKey && index < WORD_COUNT - 1) {
61-
e.preventDefault();
62-
inputRefs.current[index + 1]?.focus();
63-
}
64-
} else if (e.key === "Backspace" && words[index] === "" && index > 0) {
65-
inputRefs.current[index - 1]?.focus();
66-
}
53+
const { focusIndex, preventDefault } = getNextFocusIndex(
54+
index,
55+
e,
56+
WORD_COUNT,
57+
words[index] === "",
58+
);
59+
if (preventDefault) e.preventDefault();
60+
if (focusIndex !== null) inputRefs.current[focusIndex]?.focus();
6761
},
6862
[words],
6963
);

services/vault/src/components/simple/DepositSignContent.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ interface DepositSignContentProps {
2020
vaultProviderBtcPubkey: string;
2121
vaultKeeperBtcPubkeys: string[];
2222
universalChallengerBtcPubkeys: string[];
23-
/**
24-
* UUID of the mnemonic used for this deposit. Passed to useDepositFlow
25-
* to record the peg-in → mnemonic mapping. Note: getMnemonic is not
26-
* passed here — Lamport key submission happens via the resume flow.
27-
*/
23+
/** UUID of the stored mnemonic, used to record the peg-in → mnemonic
24+
* mapping so the resume flow can look up the correct mnemonic. */
2825
mnemonicId?: string;
2926
onSuccess: (
3027
btcTxid: string,

services/vault/src/components/simple/SimpleDeposit.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,18 +117,18 @@ function SimpleDepositContent({ open, onClose }: SimpleDepositBaseProps) {
117117
setTransactionHashes,
118118
} = useDepositPageFlow();
119119

120-
const [mnemonicId, setMnemonicId] = useState<string>();
120+
const [mnemonicId, setMnemonicId] = useState<string | null>(null);
121121

122122
const handleMnemonicComplete = useCallback(
123123
(_mnemonic?: string, id?: string) => {
124-
setMnemonicId(id);
124+
setMnemonicId(id ?? null);
125125
goToStep(DepositStep.SIGN);
126126
},
127127
[goToStep],
128128
);
129129

130130
const handleReset = useCallback(() => {
131-
setMnemonicId(undefined);
131+
setMnemonicId(null);
132132
resetDeposit();
133133
}, [resetDeposit]);
134134

@@ -227,7 +227,7 @@ function SimpleDepositContent({ open, onClose }: SimpleDepositBaseProps) {
227227
vaultProviderBtcPubkey={selectedProviderBtcPubkey}
228228
vaultKeeperBtcPubkeys={vaultKeeperBtcPubkeys}
229229
universalChallengerBtcPubkeys={universalChallengerBtcPubkeys}
230-
mnemonicId={mnemonicId}
230+
mnemonicId={mnemonicId ?? undefined}
231231
onSuccess={handleSignSuccess}
232232
onClose={onClose}
233233
onRefetchActivities={refetchActivities}

services/vault/src/hooks/deposit/useDepositFlow.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ export interface UseDepositFlowParams {
6060
vaultProviderBtcPubkey: string;
6161
vaultKeeperBtcPubkeys: string[];
6262
universalChallengerBtcPubkeys: string[];
63+
/** Callback to retrieve the decrypted mnemonic. When present, enables
64+
* Lamport PK derivation and submission to the vault provider. */
6365
getMnemonic?: () => Promise<string>;
66+
/** UUID of the stored mnemonic, used to record the peg-in → mnemonic
67+
* mapping so the resume flow can look up the correct mnemonic. */
6468
mnemonicId?: string;
6569
}
6670

services/vault/src/hooks/deposit/useMultiVaultDepositFlow.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ export interface UseMultiVaultDepositFlowParams {
8989
vaultKeeperBtcPubkeys: string[];
9090
/** Universal challenger BTC public keys */
9191
universalChallengerBtcPubkeys: string[];
92-
/** Callback to retrieve the decrypted Lamport mnemonic (depositor-as-claimer) */
92+
/** Callback to retrieve the decrypted mnemonic. When present, enables
93+
* Lamport PK derivation and submission to the vault provider. */
9394
getMnemonic?: () => Promise<string>;
94-
/** UUID of the mnemonic used for this deposit (for peg-in mapping) */
95+
/** UUID of the stored mnemonic, used to record the peg-in → mnemonic
96+
* mapping so the resume flow can look up the correct mnemonic. */
9597
mnemonicId?: string;
9698
}
9799

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { getNextFocusIndex } from "../wordGridKeyboardNav";
4+
5+
const WORD_COUNT = 12;
6+
7+
describe("getNextFocusIndex", () => {
8+
describe("Space key", () => {
9+
it("moves forward from a middle field", () => {
10+
const result = getNextFocusIndex(
11+
3,
12+
{ key: " ", shiftKey: false },
13+
WORD_COUNT,
14+
false,
15+
);
16+
expect(result).toEqual({ focusIndex: 4, preventDefault: true });
17+
});
18+
19+
it("does not move past the last field", () => {
20+
const result = getNextFocusIndex(
21+
11,
22+
{ key: " ", shiftKey: false },
23+
WORD_COUNT,
24+
false,
25+
);
26+
expect(result).toEqual({ focusIndex: null, preventDefault: true });
27+
});
28+
29+
it("moves forward from the first field", () => {
30+
const result = getNextFocusIndex(
31+
0,
32+
{ key: " ", shiftKey: false },
33+
WORD_COUNT,
34+
false,
35+
);
36+
expect(result).toEqual({ focusIndex: 1, preventDefault: true });
37+
});
38+
});
39+
40+
describe("Tab key", () => {
41+
it("moves forward on Tab", () => {
42+
const result = getNextFocusIndex(
43+
5,
44+
{ key: "Tab", shiftKey: false },
45+
WORD_COUNT,
46+
false,
47+
);
48+
expect(result).toEqual({ focusIndex: 6, preventDefault: true });
49+
});
50+
51+
it("moves backward on Shift+Tab", () => {
52+
const result = getNextFocusIndex(
53+
5,
54+
{ key: "Tab", shiftKey: true },
55+
WORD_COUNT,
56+
false,
57+
);
58+
expect(result).toEqual({ focusIndex: 4, preventDefault: true });
59+
});
60+
61+
it("lets browser handle Tab at the last field", () => {
62+
const result = getNextFocusIndex(
63+
11,
64+
{ key: "Tab", shiftKey: false },
65+
WORD_COUNT,
66+
false,
67+
);
68+
expect(result).toEqual({ focusIndex: null, preventDefault: false });
69+
});
70+
71+
it("lets browser handle Shift+Tab at the first field", () => {
72+
const result = getNextFocusIndex(
73+
0,
74+
{ key: "Tab", shiftKey: true },
75+
WORD_COUNT,
76+
false,
77+
);
78+
expect(result).toEqual({ focusIndex: null, preventDefault: false });
79+
});
80+
});
81+
82+
describe("Backspace key", () => {
83+
it("moves backward when the current field is empty", () => {
84+
const result = getNextFocusIndex(
85+
4,
86+
{ key: "Backspace", shiftKey: false },
87+
WORD_COUNT,
88+
true,
89+
);
90+
expect(result).toEqual({ focusIndex: 3, preventDefault: false });
91+
});
92+
93+
it("does nothing when the current field has text", () => {
94+
const result = getNextFocusIndex(
95+
4,
96+
{ key: "Backspace", shiftKey: false },
97+
WORD_COUNT,
98+
false,
99+
);
100+
expect(result).toEqual({ focusIndex: null, preventDefault: false });
101+
});
102+
103+
it("does nothing at the first field even if empty", () => {
104+
const result = getNextFocusIndex(
105+
0,
106+
{ key: "Backspace", shiftKey: false },
107+
WORD_COUNT,
108+
true,
109+
);
110+
expect(result).toEqual({ focusIndex: null, preventDefault: false });
111+
});
112+
});
113+
114+
describe("other keys", () => {
115+
it("returns null for unrelated keys", () => {
116+
const result = getNextFocusIndex(
117+
3,
118+
{ key: "a", shiftKey: false },
119+
WORD_COUNT,
120+
false,
121+
);
122+
expect(result).toEqual({ focusIndex: null, preventDefault: false });
123+
});
124+
});
125+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Keyboard navigation helpers for the mnemonic word grid.
3+
*
4+
* Determines which input to focus next based on keypress:
5+
* - Space → move forward
6+
* - Tab → move forward (Shift+Tab → move backward)
7+
* - Backspace on empty field → move backward
8+
*
9+
* Returns the index to focus, or `null` when the browser default
10+
* should be preserved (e.g. Tab at the last field).
11+
*/
12+
13+
export interface WordGridKeyEvent {
14+
key: string;
15+
shiftKey: boolean;
16+
}
17+
18+
export function getNextFocusIndex(
19+
index: number,
20+
event: WordGridKeyEvent,
21+
wordCount: number,
22+
isCurrentEmpty: boolean,
23+
): { focusIndex: number | null; preventDefault: boolean } {
24+
if (event.key === " ") {
25+
return {
26+
focusIndex: index < wordCount - 1 ? index + 1 : null,
27+
preventDefault: true,
28+
};
29+
}
30+
31+
if (event.key === "Tab") {
32+
if (event.shiftKey && index > 0) {
33+
return { focusIndex: index - 1, preventDefault: true };
34+
}
35+
if (!event.shiftKey && index < wordCount - 1) {
36+
return { focusIndex: index + 1, preventDefault: true };
37+
}
38+
// First/last field — let browser handle Tab normally
39+
return { focusIndex: null, preventDefault: false };
40+
}
41+
42+
if (event.key === "Backspace" && isCurrentEmpty && index > 0) {
43+
return { focusIndex: index - 1, preventDefault: false };
44+
}
45+
46+
return { focusIndex: null, preventDefault: false };
47+
}

0 commit comments

Comments
 (0)