fix(spoolbuddy): resolve react-simple-keyboard interop default so the kiosk keyboard renders (#2616)

Focusing any text field on a SpoolBuddy screen (inventory Search, or the
Search / Color Name / Brand fields on write-tag New Spool) blanked the UI
with React error #130 ("Element type is invalid ... but got: object"). It hit
both internal and Spoolman inventories, so it was not data-specific.

The SpoolBuddy shell mounts VirtualKeyboard, an on-screen keyboard that pops up
on focusin for any input -- so every field on every SpoolBuddy page tripped it,
while the main app (no on-screen keyboard) was fine. VirtualKeyboard imports the
default export of react-simple-keyboard, a CommonJS package; under the current
bundler's CJS->ESM interop that default resolves to the module namespace object
({ KeyboardReact, default }) rather than the component, so <Keyboard> renders an
object as an element type and React throws. vitest's interop returns the real
component, so it only manifested in the browser build -- a runtime, not a type,
problem.

Add a small resolveInteropDefault helper that unwraps such an interop-wrapped
default: it returns the value as-is when already a usable element type
(function/class, tag string, or a $$typeof-marked forwardRef/memo/lazy) and
otherwise falls through to .default and named exports. VirtualKeyboard resolves
the real component through it.
This commit is contained in:
maziggy
2026-07-20 13:41:41 +02:00
parent 258db95483
commit 585b1be054
7 changed files with 294 additions and 152 deletions
+14 -1
View File
@@ -1,7 +1,20 @@
import { useEffect, useRef, useState, useCallback } from 'react';
import Keyboard from 'react-simple-keyboard';
import KeyboardImport from 'react-simple-keyboard';
import 'react-simple-keyboard/build/css/index.css';
import './VirtualKeyboard.css';
import { resolveInteropDefault } from '../utils/interopDefault';
// react-simple-keyboard is published as CommonJS. Depending on the bundler's
// CJS->ESM interop, the default import arrives either as the Keyboard component
// itself or as the module namespace object ({ KeyboardReact, default }). Under
// the current Vite build (and Node's ESM loader) it's the latter, so rendering
// <KeyboardImport> puts an object where an element type belongs and React throws
// "Element type is invalid ... got: object" (#130) — crashing every SpoolBuddy
// screen the instant a text input is focused and this keyboard mounts (#2616).
// Resolve the real component defensively so it renders under any interop shape.
// The TYPE of the default import is already the component (from the .d.ts), so
// the cast keeps JSX + ref typing intact while fixing only the runtime value.
const Keyboard = resolveInteropDefault<typeof KeyboardImport>(KeyboardImport, ['KeyboardReact']);
const FOCUSABLE_TYPES = new Set(['text', 'password', 'email', 'search', 'url', 'number']);