Fix virtual keyboard overlaying input fields on SpoolBuddy settings

When the virtual keyboard opened on the SpoolBuddy device tab, the
  Backend URL and API Token fields were hidden behind the keyboard
  overlay. The keyboard now adds temporary bottom padding to the
  scrollable container so inputs scroll above it, and removes the
  padding on dismiss.
This commit is contained in:
maziggy
2026-03-26 10:54:19 +01:00
parent bebfc01167
commit 00b4386e90
4 changed files with 34 additions and 6 deletions
+1
View File
@@ -21,6 +21,7 @@ All notable changes to Bambuddy will be documented in this file.
- **Spool Assignment Changes Sync Across Tabs** — Assigning or unassigning a spool now broadcasts a WebSocket event to all connected clients. Other open browser tabs and the SpoolBuddy frontend update automatically without requiring a page reload.
- **SpoolBuddy Auto-Navigate on Tag Scan** — When an NFC tag is detected while the SpoolBuddy UI is on a non-dashboard page (Settings, AMS, Write Tag, etc.), the frontend automatically navigates back to the main dashboard to show the scanned spool. Also wakes the screen if the display was blanked.
- **SpoolBuddy Swipe to Switch Printers** — Swiping left/right on the SpoolBuddy touchscreen now cycles through online printers instead of triggering browser back/forward navigation. The selected printer updates in the top bar dropdown. Requires at least two online printers; single-printer setups are unaffected.
- **SpoolBuddy Virtual Keyboard No Longer Overlays Input Fields** — The virtual keyboard now adds temporary scroll padding to the content area when it opens, ensuring the focused input field scrolls above the keyboard instead of being hidden behind it. Fixes text entry on the SpoolBuddy Settings device tab (backend URL, API token fields).
- **SpoolBuddy System Tab** — Added a "System" tab to SpoolBuddy Settings showing live OS stats from the Raspberry Pi: CPU temperature, core count, load average, memory usage, disk usage, OS distro/kernel/architecture, Python version, and system uptime. Stats are collected by the daemon every heartbeat (10s) using stdlib-only reads from `/proc` and `/sys` — no additional dependencies required. Usage bars turn amber at 70% and red at 90%; CPU temperature is color-coded green/amber/red.
### Fixed
+31 -4
View File
@@ -26,6 +26,30 @@ 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;
}
}, []);
const handleFocusIn = useCallback((e: FocusEvent) => {
if (closingRef.current) return;
const target = e.target as HTMLElement;
@@ -47,11 +71,12 @@ export function VirtualKeyboard() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(keyboardRef.current as any)?.setInput?.(activeInput.current.value);
// Scroll input into view above the keyboard
// Add scroll padding then scroll input into view above the keyboard
addScrollPadding(target);
setTimeout(() => {
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
}, []);
}, [addScrollPadding]);
const handleFocusOut = useCallback(() => {
// Delay to allow click on keyboard buttons to register
@@ -64,10 +89,11 @@ export function VirtualKeyboard() {
) {
return;
}
removeScrollPadding();
setVisible(false);
activeInput.current = null;
}, 150);
}, []);
}, [removeScrollPadding]);
useEffect(() => {
document.addEventListener('focusin', handleFocusIn);
@@ -83,6 +109,7 @@ export function VirtualKeyboard() {
const dismiss = useCallback(() => {
closingRef.current = true;
setClosing(true);
removeScrollPadding();
activeInput.current?.blur();
activeInput.current = null;
setTimeout(() => {
@@ -90,7 +117,7 @@ export function VirtualKeyboard() {
setClosing(false);
closingRef.current = false;
}, 400);
}, []);
}, [removeScrollPadding]);
const onKeyPress = useCallback((button: string) => {
const input = activeInput.current;
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -23,7 +23,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-B2-uBp6-.js"></script>
<script type="module" crossorigin src="/assets/index-C7t_l7cK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CZLTApPU.css">
</head>
<body>