feat: implement one-shot modifier keys for mobile terminal
- Redesign useSpecialKeys hook with modifier state tracking - Add one-shot activation for Ctrl and Alt keys - Visual feedback: active modifiers shown with yellow highlight - Fix focusInput to use term.focus() instead of hidden input - Always refocus terminal after sending any special key - Add requestAnimationFrame for reliable focus restoration
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
export type SpecialKey =
|
||||
| "escape"
|
||||
@@ -29,6 +29,8 @@ export type SpecialKey =
|
||||
| "f11"
|
||||
| "f12";
|
||||
|
||||
export type ModifierKey = "ctrl" | "alt";
|
||||
|
||||
const KEY_SEQUENCES: Record<SpecialKey, string> = {
|
||||
escape: "\x1B",
|
||||
tab: "\t",
|
||||
@@ -59,22 +61,89 @@ const KEY_SEQUENCES: Record<SpecialKey, string> = {
|
||||
f12: "\x1B[24~",
|
||||
};
|
||||
|
||||
// Single character sequences with modifier prefixes
|
||||
const MODIFIER_PREFIXES: Record<string, { ctrl: string; alt: string; ctrlAlt: string }> = {
|
||||
// Letters
|
||||
a: { ctrl: "\x01", alt: "\x1Ba", ctrlAlt: "\x1B\x01" },
|
||||
b: { ctrl: "\x02", alt: "\x1Bb", ctrlAlt: "\x1B\x02" },
|
||||
c: { ctrl: "\x03", alt: "\x1Bc", ctrlAlt: "\x1B\x03" },
|
||||
d: { ctrl: "\x04", alt: "\x1Bd", ctrlAlt: "\x1B\x04" },
|
||||
e: { ctrl: "\x05", alt: "\x1Be", ctrlAlt: "\x1B\x05" },
|
||||
f: { ctrl: "\x06", alt: "\x1Bf", ctrlAlt: "\x1B\x06" },
|
||||
g: { ctrl: "\x07", alt: "\x1Bg", ctrlAlt: "\x1B\x07" },
|
||||
h: { ctrl: "\x08", alt: "\x1Bh", ctrlAlt: "\x1B\x08" },
|
||||
i: { ctrl: "\x09", alt: "\x1Bi", ctrlAlt: "\x1B\x09" },
|
||||
j: { ctrl: "\x0A", alt: "\x1Bj", ctrlAlt: "\x1B\x0A" },
|
||||
k: { ctrl: "\x0B", alt: "\x1Bk", ctrlAlt: "\x1B\x0B" },
|
||||
l: { ctrl: "\x0C", alt: "\x1Bl", ctrlAlt: "\x1B\x0C" },
|
||||
m: { ctrl: "\x0D", alt: "\x1Bm", ctrlAlt: "\x1B\x0D" },
|
||||
n: { ctrl: "\x0E", alt: "\x1Bn", ctrlAlt: "\x1B\x0E" },
|
||||
o: { ctrl: "\x0F", alt: "\x1Bo", ctrlAlt: "\x1B\x0F" },
|
||||
p: { ctrl: "\x10", alt: "\x1Bp", ctrlAlt: "\x1B\x10" },
|
||||
q: { ctrl: "\x11", alt: "\x1Bq", ctrlAlt: "\x1B\x11" },
|
||||
r: { ctrl: "\x12", alt: "\x1Br", ctrlAlt: "\x1B\x12" },
|
||||
s: { ctrl: "\x13", alt: "\x1Bs", ctrlAlt: "\x1B\x13" },
|
||||
t: { ctrl: "\x14", alt: "\x1Bt", ctrlAlt: "\x1B\x14" },
|
||||
u: { ctrl: "\x15", alt: "\x1Bu", ctrlAlt: "\x1B\x15" },
|
||||
v: { ctrl: "\x16", alt: "\x1Bv", ctrlAlt: "\x1B\x16" },
|
||||
w: { ctrl: "\x17", alt: "\x1Bw", ctrlAlt: "\x1B\x17" },
|
||||
x: { ctrl: "\x18", alt: "\x1Bx", ctrlAlt: "\x1B\x18" },
|
||||
y: { ctrl: "\x19", alt: "\x1By", ctrlAlt: "\x1B\x19" },
|
||||
z: { ctrl: "\x1A", alt: "\x1Bz", ctrlAlt: "\x1B\x1A" },
|
||||
// Numbers
|
||||
"0": { ctrl: "0", alt: "\x1B0", ctrlAlt: "\x1B0" },
|
||||
"1": { ctrl: "1", alt: "\x1B1", ctrlAlt: "\x1B1" },
|
||||
"2": { ctrl: "\x00", alt: "\x1B2", ctrlAlt: "\x1B\x00" },
|
||||
"3": { ctrl: "\x1B", alt: "\x1B3", ctrlAlt: "\x1B\x1B" },
|
||||
"4": { ctrl: "\x1C", alt: "\x1B4", ctrlAlt: "\x1B\x1C" },
|
||||
"5": { ctrl: "\x1D", alt: "\x1B5", ctrlAlt: "\x1B\x1D" },
|
||||
"6": { ctrl: "\x1E", alt: "\x1B6", ctrlAlt: "\x1B\x1E" },
|
||||
"7": { ctrl: "\x1F", alt: "\x1B7", ctrlAlt: "\x1B\x1F" },
|
||||
"8": { ctrl: "\x7F", alt: "\x1B8", ctrlAlt: "\x1B\x7F" },
|
||||
"9": { ctrl: "9", alt: "\x1B9", ctrlAlt: "\x1B9" },
|
||||
};
|
||||
|
||||
interface UseSpecialKeysOptions {
|
||||
onSend: (data: string) => void;
|
||||
}
|
||||
|
||||
export function useSpecialKeys({ onSend }: UseSpecialKeysOptions) {
|
||||
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(null);
|
||||
|
||||
const sendKey = useCallback(
|
||||
(key: SpecialKey) => {
|
||||
const sequence = KEY_SEQUENCES[key];
|
||||
if (sequence) {
|
||||
onSend(sequence);
|
||||
// Handle modifier keys (one-shot)
|
||||
if (key === "ctrl" || key === "alt") {
|
||||
// Toggle modifier
|
||||
setActiveModifier((current) => (current === key ? null : key));
|
||||
return;
|
||||
}
|
||||
|
||||
const sequence = KEY_SEQUENCES[key];
|
||||
if (!sequence) return;
|
||||
|
||||
// Check if we have an active modifier and the key is a single character
|
||||
if (activeModifier && sequence.length === 1) {
|
||||
const char = sequence;
|
||||
const mapping = MODIFIER_PREFIXES[char.toLowerCase()];
|
||||
if (mapping) {
|
||||
onSend(mapping[activeModifier]);
|
||||
setActiveModifier(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onSend(sequence);
|
||||
setActiveModifier(null);
|
||||
},
|
||||
[onSend]
|
||||
[onSend, activeModifier]
|
||||
);
|
||||
|
||||
return { sendKey };
|
||||
const clearModifier = useCallback(() => {
|
||||
setActiveModifier(null);
|
||||
}, []);
|
||||
|
||||
return { sendKey, activeModifier, clearModifier };
|
||||
}
|
||||
|
||||
export { KEY_SEQUENCES };
|
||||
|
||||
Reference in New Issue
Block a user