|
|
@@ -3,7 +3,7 @@ import Keyboard from 'react-simple-keyboard';
|
|
|
import 'react-simple-keyboard/build/css/index.css';
|
|
|
import './VirtualKeyboard.css';
|
|
|
|
|
|
-const FOCUSABLE_TYPES = new Set(['text', 'password', 'email', 'search', 'url']);
|
|
|
+const FOCUSABLE_TYPES = new Set(['text', 'password', 'email', 'search', 'url', 'number']);
|
|
|
|
|
|
/**
|
|
|
* Set value on a controlled React input using the native setter,
|
|
|
@@ -17,7 +17,7 @@ function setNativeValue(input: HTMLInputElement | HTMLTextAreaElement, value: st
|
|
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
}
|
|
|
|
|
|
-export function VirtualKeyboard() {
|
|
|
+export function VirtualKeyboard({ onVisibilityChange }: { onVisibilityChange?: (visible: boolean) => void }) {
|
|
|
const [visible, setVisible] = useState(false);
|
|
|
const [closing, setClosing] = useState(false);
|
|
|
const closingRef = useRef(false);
|
|
|
@@ -26,29 +26,10 @@ export function VirtualKeyboard() {
|
|
|
const keyboardRef = useRef<ReturnType<typeof Keyboard> | null>(null);
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
- // Add bottom padding to the scrollable ancestor so inputs can scroll above the keyboard
|
|
|
- const paddedParentRef = useRef<HTMLElement | null>(null);
|
|
|
-
|
|
|
- const addScrollPadding = useCallback((target: HTMLElement) => {
|
|
|
- // Find nearest scrollable ancestor
|
|
|
- let el: HTMLElement | null = target.parentElement;
|
|
|
- while (el) {
|
|
|
- const style = getComputedStyle(el);
|
|
|
- if (style.overflowY === 'auto' || style.overflowY === 'scroll') break;
|
|
|
- el = el.parentElement;
|
|
|
- }
|
|
|
- if (!el) return;
|
|
|
- paddedParentRef.current = el;
|
|
|
- // Keyboard is ~260px tall; add generous padding
|
|
|
- el.style.paddingBottom = '280px';
|
|
|
- }, []);
|
|
|
-
|
|
|
- const removeScrollPadding = useCallback(() => {
|
|
|
- if (paddedParentRef.current) {
|
|
|
- paddedParentRef.current.style.paddingBottom = '';
|
|
|
- paddedParentRef.current = null;
|
|
|
- }
|
|
|
- }, []);
|
|
|
+ // Notify parent when keyboard visibility changes
|
|
|
+ useEffect(() => {
|
|
|
+ onVisibilityChange?.(visible);
|
|
|
+ }, [visible, onVisibilityChange]);
|
|
|
|
|
|
const handleFocusIn = useCallback((e: FocusEvent) => {
|
|
|
if (closingRef.current) return;
|
|
|
@@ -71,14 +52,12 @@ export function VirtualKeyboard() {
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
(keyboardRef.current as any)?.setInput?.(activeInput.current.value);
|
|
|
|
|
|
- // Add scroll padding then scroll the input's parent card into view above the keyboard
|
|
|
- addScrollPadding(target);
|
|
|
+ // Scroll focused input into view after the keyboard renders and layout reflows
|
|
|
setTimeout(() => {
|
|
|
- // Scroll the closest card/section so the entire group of fields is visible
|
|
|
const card = target.closest('.bg-zinc-800, .rounded-lg, [data-vkb-group]') as HTMLElement | null;
|
|
|
- (card ?? target).scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
|
+ (card ?? target).scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
|
}, 100);
|
|
|
- }, [addScrollPadding]);
|
|
|
+ }, []);
|
|
|
|
|
|
const handleFocusOut = useCallback(() => {
|
|
|
// Delay to allow click on keyboard buttons to register
|
|
|
@@ -91,11 +70,10 @@ export function VirtualKeyboard() {
|
|
|
) {
|
|
|
return;
|
|
|
}
|
|
|
- removeScrollPadding();
|
|
|
setVisible(false);
|
|
|
activeInput.current = null;
|
|
|
}, 150);
|
|
|
- }, [removeScrollPadding]);
|
|
|
+ }, []);
|
|
|
|
|
|
useEffect(() => {
|
|
|
document.addEventListener('focusin', handleFocusIn);
|
|
|
@@ -111,7 +89,6 @@ export function VirtualKeyboard() {
|
|
|
const dismiss = useCallback(() => {
|
|
|
closingRef.current = true;
|
|
|
setClosing(true);
|
|
|
- removeScrollPadding();
|
|
|
activeInput.current?.blur();
|
|
|
activeInput.current = null;
|
|
|
setTimeout(() => {
|
|
|
@@ -119,7 +96,7 @@ export function VirtualKeyboard() {
|
|
|
setClosing(false);
|
|
|
closingRef.current = false;
|
|
|
}, 400);
|
|
|
- }, [removeScrollPadding]);
|
|
|
+ }, []);
|
|
|
|
|
|
const onKeyPress = useCallback((button: string) => {
|
|
|
const input = activeInput.current;
|
|
|
@@ -171,7 +148,7 @@ export function VirtualKeyboard() {
|
|
|
{!closing && (
|
|
|
<div
|
|
|
ref={containerRef}
|
|
|
- className="fixed bottom-0 left-0 right-0 z-[9999]"
|
|
|
+ className="relative z-[9999] shrink-0"
|
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
|
onTouchStart={(e) => {
|
|
|
// Prevent focus loss but allow button interaction
|