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