fix: keep virtual keyboard open when tapping special keys
- Use onPointerDown with preventDefault() instead of onClick - Add onKeepFocus callback to SpecialKeysStrip and SpecialKeysPanel - Expose focusInput via onTerminalReady in TerminalComponent - MobileTerminalWrapper passes focus callback to keep keyboard open
This commit is contained in:
@@ -29,6 +29,7 @@ export const MobileTerminalWrapper: React.FC<MobileTerminalWrapperProps> = ({
|
||||
const [terminalRef, setTerminalRef] = useState<{
|
||||
sendData: (data: string) => void;
|
||||
connectionStatus: "connecting" | "connected" | "disconnected" | "error";
|
||||
focusInput: () => void;
|
||||
} | null>(null);
|
||||
|
||||
const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile });
|
||||
@@ -40,8 +41,8 @@ export const MobileTerminalWrapper: React.FC<MobileTerminalWrapperProps> = ({
|
||||
}, [headerAutoHide, keysAutoHide]);
|
||||
|
||||
const handleTerminalReady = useCallback(
|
||||
(sendData: (data: string) => void, connectionStatus: "connecting" | "connected" | "disconnected" | "error") => {
|
||||
setTerminalRef({ sendData, connectionStatus });
|
||||
(sendData: (data: string) => void, connectionStatus: "connecting" | "connected" | "disconnected" | "error", focusInput: () => void) => {
|
||||
setTerminalRef({ sendData, connectionStatus, focusInput });
|
||||
},
|
||||
[]
|
||||
);
|
||||
@@ -93,12 +94,14 @@ export const MobileTerminalWrapper: React.FC<MobileTerminalWrapperProps> = ({
|
||||
onSend={handleSendKey}
|
||||
isVisible={keysAutoHide.isVisible && !showPanel}
|
||||
onMoreClick={() => setShowPanel(true)}
|
||||
onKeepFocus={() => terminalRef?.focusInput()}
|
||||
/>
|
||||
|
||||
<SpecialKeysPanel
|
||||
onSend={handleSendKey}
|
||||
isOpen={showPanel}
|
||||
onClose={() => setShowPanel(false)}
|
||||
onKeepFocus={() => terminalRef?.focusInput()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ interface SpecialKeysPanelProps {
|
||||
onSend: (data: string) => void;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onKeepFocus?: () => void;
|
||||
}
|
||||
|
||||
const EXPANDED_KEYS: { key: SpecialKey; label: string }[] = [
|
||||
@@ -36,11 +37,19 @@ export const SpecialKeysPanel: React.FC<SpecialKeysPanelProps> = ({
|
||||
onSend,
|
||||
isOpen,
|
||||
onClose,
|
||||
onKeepFocus,
|
||||
}) => {
|
||||
const { sendKey } = useSpecialKeys({ onSend });
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handlePointerDown = (e: React.PointerEvent, key: SpecialKey) => {
|
||||
e.preventDefault();
|
||||
sendKey(key);
|
||||
onClose();
|
||||
onKeepFocus?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="special-keys-panel-overlay" onClick={onClose}>
|
||||
<div
|
||||
@@ -52,10 +61,7 @@ export const SpecialKeysPanel: React.FC<SpecialKeysPanelProps> = ({
|
||||
<button
|
||||
key={key}
|
||||
className="special-key-button"
|
||||
onClick={() => {
|
||||
sendKey(key);
|
||||
onClose();
|
||||
}}
|
||||
onPointerDown={(e) => handlePointerDown(e, key)}
|
||||
type="button"
|
||||
>
|
||||
{label}
|
||||
@@ -68,10 +74,7 @@ export const SpecialKeysPanel: React.FC<SpecialKeysPanelProps> = ({
|
||||
<button
|
||||
key={key}
|
||||
className="special-key-button"
|
||||
onClick={() => {
|
||||
sendKey(key);
|
||||
onClose();
|
||||
}}
|
||||
onPointerDown={(e) => handlePointerDown(e, key)}
|
||||
type="button"
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -5,6 +5,7 @@ interface SpecialKeysStripProps {
|
||||
onSend: (data: string) => void;
|
||||
isVisible: boolean;
|
||||
onMoreClick?: () => void;
|
||||
onKeepFocus?: () => void;
|
||||
}
|
||||
|
||||
const PRIMARY_KEYS: { key: SpecialKey; label: string }[] = [
|
||||
@@ -22,16 +23,29 @@ export const SpecialKeysStrip: React.FC<SpecialKeysStripProps> = ({
|
||||
onSend,
|
||||
isVisible,
|
||||
onMoreClick,
|
||||
onKeepFocus,
|
||||
}) => {
|
||||
const { sendKey } = useSpecialKeys({ onSend });
|
||||
|
||||
const handlePointerDown = (e: React.PointerEvent, key: SpecialKey) => {
|
||||
e.preventDefault();
|
||||
sendKey(key);
|
||||
onKeepFocus?.();
|
||||
};
|
||||
|
||||
const handleMorePointerDown = (e: React.PointerEvent) => {
|
||||
e.preventDefault();
|
||||
onMoreClick?.();
|
||||
onKeepFocus?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`special-keys-strip ${isVisible ? "visible" : "hidden"}`}>
|
||||
{PRIMARY_KEYS.map(({ key, label }) => (
|
||||
<button
|
||||
key={key}
|
||||
className="special-key-button"
|
||||
onClick={() => sendKey(key)}
|
||||
onPointerDown={(e) => handlePointerDown(e, key)}
|
||||
type="button"
|
||||
aria-label={`Send ${label}`}
|
||||
>
|
||||
@@ -41,7 +55,7 @@ export const SpecialKeysStrip: React.FC<SpecialKeysStripProps> = ({
|
||||
{onMoreClick && (
|
||||
<button
|
||||
className="special-key-button special-key-more"
|
||||
onClick={onMoreClick}
|
||||
onPointerDown={handleMorePointerDown}
|
||||
type="button"
|
||||
aria-label="More special keys"
|
||||
>
|
||||
|
||||
@@ -10,7 +10,8 @@ interface TerminalProps {
|
||||
isMobile?: boolean;
|
||||
onTerminalReady?: (
|
||||
sendData: (data: string) => void,
|
||||
connectionStatus: "connecting" | "connected" | "disconnected" | "error"
|
||||
connectionStatus: "connecting" | "connected" | "disconnected" | "error",
|
||||
focusInput: () => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
@@ -196,7 +197,10 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
||||
ws.send(data);
|
||||
}
|
||||
};
|
||||
onTerminalReadyRef.current(sendData, status);
|
||||
const focusInput = () => {
|
||||
hiddenInputRef.current?.focus();
|
||||
};
|
||||
onTerminalReadyRef.current(sendData, status, focusInput);
|
||||
}
|
||||
|
||||
// Visibility API for reconnection
|
||||
@@ -225,7 +229,10 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
||||
wsRef.current.send(data);
|
||||
}
|
||||
};
|
||||
onTerminalReady(sendData, status);
|
||||
const focusInput = () => {
|
||||
hiddenInputRef.current?.focus();
|
||||
};
|
||||
onTerminalReady(sendData, status, focusInput);
|
||||
}
|
||||
}, [status, onTerminalReady]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user