expo-key-event
Version:
Provides an interface for reading key events such as from external bluetooth keyboards on Android, iOS and Web.
155 lines (106 loc) • 5.84 kB
Markdown
# GEMINI.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
expo-key-event is an Expo module that provides a unified interface for capturing hardware keyboard events from external devices (Bluetooth keyboards, gamepads) across iOS, macOS, Android, and Web platforms.
**Key Requirement:** Expo SDK >= 52 (uses `useEvent` API introduced in SDK 52)
## Development Commands
### Main Module Development
```bash
npm run build # Build the module (expo-module build)
npm run lint # Lint the module (expo-module lint)
npm run test # Test the module (expo-module test)
npm run clean # Clean build artifacts
npm run prepare # Prepare module for development
npm run release # Create a new release (uses release-it)
```
### Example App Development
```bash
cd example
npm run ios # Run example app on iOS
npm run android # Run example app on Android
npm run web # Run example app on Web
```
### Opening Native Projects
```bash
npm run open:ios # Open iOS project in Xcode
npm run open:android # Open Android project in Android Studio
```
## Architecture
### Platform-Specific Native Modules
The module uses **platform-specific implementations** to capture raw key events from the OS:
#### iOS/macOS (Swift)
- Located in: `ios/ExpoKeyEventModule.swift`
- Uses a hidden `KeyboardListenerView` (UIView/NSView) that becomes first responder
- iOS: Captures events via `pressesBegan(_:with:)` using `UIPress.key.keyCode`
- macOS: Captures events via `keyDown(with:)` using `NSEvent.keyCode`
- Returns raw key codes as strings (e.g., "4", "79")
#### Android (Kotlin)
- Located in: `android/src/main/java/expo/modules/keyevent/`
- `ExpoKeyEventModule.kt`: Main module that manages view lifecycle
- `ExpoKeyEventView.kt`: Custom `ExpoView` that captures key events
- Overrides `onKeyDown()` to intercept Android `KeyEvent`
- Returns Android key codes as strings (e.g., "29", "7")
- Only processes initial key down (filters repeats via `event.repeatCount == 0`)
#### Web (TypeScript)
- Located in: `src/hooks/useKeyEvent.web.ts`
- Uses browser's native `addEventListener("keydown")` API
- Directly returns Web standard key codes (e.g., "KeyA", "ArrowUp")
### Key Code Unification Layer
Each platform returns **different raw key codes** for the same physical key. The unification layer translates these to Web standard codes:
- **Key Mapping Files:** `src/constants/KeyCodeMapping.{ios,macos,android}.ts`
- Map platform-specific numeric codes to Web standard strings
- Example: iOS "4" → "KeyA", Android "29" → "KeyA"
- These files contain large lookup tables (100+ entries each)
- **Unification Utility:** `src/utils/unifyKeyCode.ts`
- Applies platform-specific mapping via `KeyCodeMapping` object
- Platform-specific imports resolved at build time
- Warns in dev mode if mapping not found
- **Web Platform:** No mapping needed (already uses standard codes)
### React Hooks API
Two hooks provide different patterns for consuming key events:
#### `useKeyEvent` (State Management)
- Located in: `src/hooks/useKeyEvent.ts` (native) / `.web.ts` (web)
- **Manages state internally** using Expo's `useEvent` hook (native) or `useState` (web)
- Returns `{ keyEvent, startListening, stopListening }`
- `keyEvent` is always the **latest** key press
- Best for: Simple use cases where you just need the current key
#### `useKeyEventListener` (Event Handler)
- Located in: `src/hooks/useKeyEventListener.ts` (native)
- **No state management** - just calls your callback
- Takes a callback function as parameter
- Returns `{ startListening, stopListening }`
- Best for: Custom state management, side effects, logging
Both hooks support:
- `listenOnMount` parameter (default: true) - Auto-start listening
- `preventReload` parameter (default: false) - Block 'R' key reload in dev mode
- Dev mode feature: Pressing 'R' triggers `DevSettings.reload()` unless prevented
### Module Entry Points
- **Public API:** `src/index.ts` - Exports both hooks and types
- **Native Module:** `src/ExpoKeyEventModule.ts` - Bridge to native code
- Provides `startListening()` and `stopListening()` functions
- Emits `onKeyPress` events to JS
- Throws helpful error if native module unavailable (Expo Go detection)
- **Type Definitions:** `src/ExpoKeyEvent.types.ts`
- `KeyPressEvent`: `{ key: string }` (unified key code)
- `ExpoKeyEventModuleEvents`: Event emitter types
## Important Development Notes
### Native Development Requirements
**This module requires a development build** - it will NOT work with Expo Go:
- Use `npx expo run:ios` instead of `npx expo start`
- Use `npx expo run:android` instead of `npx expo start`
- See troubleshooting section in README.md for details
### Testing on Simulators/Emulators
- **iOS Simulator:** Ensure "Hardware → Keyboard → Connect Hardware Keyboard" is enabled
- **Android Emulator:** Does not support Bluetooth/USB hardware keyboards
- Use `adb shell input keyevent <code>` to simulate key events
- Or use a physical device for testing
### Key Code Discovery
When adding new key mappings:
1. The module logs warnings in dev mode for unmapped keys: `"No mapping found for keyCode: X"`
2. Platform key codes can be found at:
- iOS: [UIKit HID Usage constants](https://developer.apple.com/documentation/uikit/uikeyboardhidusage)
- Android: [KeyEvent constants](https://developer.android.com/reference/android/view/KeyEvent)
- Web: [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code)
## Module Configuration
- **expo-module.config.json:** Defines platforms and module names
- **expo-module-scripts:** Used for building, testing, linting (see package.json scripts)