Go to file
Michał Sieciechowicz 48cc0e1fc1 safe-mode for input fields 2025-08-05 22:13:56 +02:00
.angular/cache/20.1.3/ng-packagr initial commit 2025-08-01 19:34:19 +02:00
src safe-mode for input fields 2025-08-05 22:13:56 +02:00
.gitignore KeyItem event bindings 2025-08-02 18:57:16 +02:00
.npmignore KeyItem event bindings 2025-08-02 18:57:16 +02:00
LICENSE KeyItem event bindings 2025-08-02 18:57:16 +02:00
README.md KeyItem event bindings 2025-08-02 18:57:16 +02:00
angular.json initial commit 2025-08-01 19:34:19 +02:00
ng-package.json initial commit 2025-08-01 19:34:19 +02:00
package.json KeyItem event bindings 2025-08-02 18:57:16 +02:00
tsconfig.lib.json initial commit 2025-08-01 19:34:19 +02:00
tsconfig.lib.prod.json initial commit 2025-08-01 19:34:19 +02:00
tsconfig.spec.json initial commit 2025-08-01 19:34:19 +02:00

README.md

@workshack/input

Workshack input components library with gamepad and keyboard navigation support for Angular applications.

Installation

npm install @workshack/input

Services

InputService

Description: Core service for managing input devices, actions, and schemes.

Usage:

import { InputService } from '@workshack/input';

@Component({
  // ...
})
export class MyComponent {
  constructor(private inputService: InputService) {
    // Define custom actions
    const jumpAction = this.inputService.defineAction('jump');
    jumpAction.onDown(() => console.log('Jump pressed!'));
    jumpAction.onUp(() => console.log('Jump released!'));
    
    // Create input scheme
    const gameScheme = this.inputService.defineScheme('game');
    
    // Listen for device changes
    this.inputService.deviceListChanged.subscribe(devices => {
      console.log('Available devices:', devices);
    });
  }
}

Methods:

  • defineAction(name: string): InputAction - Creates a new input action
  • defineScheme(name: string): InputScheme - Creates a new input scheme
  • getDevices(): InputDevice[] - Returns list of available input devices

Properties:

  • actions: InputAction[] - Array of defined actions
  • schemes: InputScheme[] - Array of defined schemes
  • devices: InputDevice[] - Array of connected devices
  • deviceListChanged: BehaviorSubject<InputDevice[]> - Observable for device changes

ControlService (Example Integration)

Description: Higher-level service that integrates with InputService to provide common navigation actions.

Usage:

import { ControlService } from 'your-app/services/control.service';

@Component({
  // ...
})
export class MyComponent {
  constructor(private controlService: ControlService) {
    // Service automatically sets up common actions:
    // - Left, Right, Up, Down navigation
    // - Accept, Back, Exit actions
    // - Hint/Help action
    
    // Get key names for display
    const keyNames = this.controlService.getKeyNames('Enter', 'Escape');
    console.log('Key names:', keyNames); // ['Enter', 'Escape'] or themed names
  }
}

Static Properties:

  • keyLeft: InputAction - Left navigation action
  • keyRight: InputAction - Right navigation action
  • keyUp: InputAction - Up navigation action
  • keyDown: InputAction - Down navigation action
  • keyAccept: InputAction - Accept/confirm action
  • keyBack: InputAction - Back/cancel action
  • keyExit: InputAction - Exit action
  • keyHint: InputAction - Hint/help action

Methods:

  • getKeyNames(...names): string[] - Get themed key names for display
  • clearKeyBindings() - Clear all key bindings from schemes
  • bindActionsToScheme(device: InputDevice) - Bind actions to device scheme

Directives

ActionBindingDirective

Selector: [inputActionBinding]
Description: Binds an action name to an HTML element.

Usage:

<button inputActionBinding="confirm">Confirm</button>
<div inputActionBinding="back">Back</div>

Properties:

  • inputActionBinding: string - Action name to bind to the element

Features:

  • Automatically sets action attribute on the element
  • Works with keyboard and gamepad navigation

KeyItemDirective

Selector: [inputKeyItem]
Description: Makes an element navigable with keyboard/gamepad and handles focus states.

Usage:

<button 
  inputKeyItem
  [active]="'focused'"
  [actionBindings]="{
    Accept: () => onClick(),
    Back: () => onCancel()
  }"
  (activated)="onActivated()"
  (focused)="onFocused()"
  (blurred)="onBlurred()">
  Click me
</button>

Properties:

  • active: string - CSS class name for active/focused state (default: 'active')
  • actionBindings: ActionBindings - Object mapping action names to callback functions

Events:

  • activated: EventEmitter<void> - Emitted when item is activated
  • deactivated: EventEmitter<void> - Emitted when item is deactivated
  • focused: EventEmitter<void> - Emitted when item receives focus
  • blurred: EventEmitter<void> - Emitted when item loses focus

Static Methods:

  • FireAction(action: string): boolean - Fires action on currently focused item
  • ActivateCurrentItem() - Activates the currently focused item
  • SelectNexItem(fromAngle: number, toAngle: number) - Selects next item in direction

KeyItemGroupDirective

Selector: [inputKeyItemGroup]
Description: Groups KeyItem elements for scoped navigation.

Usage:

<div inputKeyItemGroup>
  <button inputKeyItem>Button 1</button>
  <button inputKeyItem>Button 2</button>
  <button inputKeyItem>Button 3</button>
</div>

Features:

  • Navigation is scoped to items within the group
  • Automatically manages focus within the group
  • Supports nested groups

Models

InputAction

Description: Represents a bindable input action (e.g., "jump", "fire", "menu").

Usage:

const jumpAction = new InputAction('jump');

// Bind callbacks
jumpAction.onDown((value, data, angle, timePressed) => {
  console.log('Jump started!', { value, data, angle, timePressed });
});

jumpAction.onUp((value, data, angle, timePressed) => {
  console.log('Jump ended!', { value, data, angle, timePressed });
});

jumpAction.onChange((value, data, angle, timePressed) => {
  console.log('Jump value changed:', value);
});

// Fire the action
jumpAction.fire(1.0); // Press
jumpAction.fire(0.0); // Release

Methods:

  • onDown(callback: ActionCallback) - Register callback for action press
  • onUp(callback: ActionCallback) - Register callback for action release
  • onChange(callback: ActionCallback) - Register callback for value changes
  • fire(value: number, data?: unknown, angle?: number, timePressed?: number) - Fire the action
  • mapKeysToScheme(scheme: InputScheme, keys: (string|number)[]) - Map keys to scheme

InputDevice

Description: Base class for input devices (keyboard, gamepad).

Properties:

  • name: string - Device name
  • type: string - Device type ('keyboard', 'gamepad')

Methods:

  • hasScheme(): boolean - Check if device has an input scheme
  • mapKey(action: InputAction, keys: (string|number)[]) - Map keys to action

InputKeyboard

Description: Keyboard input device implementation.

Usage:

const keyboard = new InputKeyboard('Main Keyboard');
keyboard.mapKey(jumpAction, ['Space', 'KeyW']);

InputGamepad

Description: Gamepad input device implementation with automatic detection.

Static Properties:

  • DeviceConnected: Subject<InputGamepad> - Observable for gamepad connections
  • DeviceDisconnected: Subject<InputGamepad> - Observable for gamepad disconnections

Static Methods:

  • Init() - Initialize gamepad detection

Usage:

// Listen for gamepad events
InputGamepad.DeviceConnected.subscribe(gamepad => {
  console.log('Gamepad connected:', gamepad.name);
  gamepad.mapKey(jumpAction, [0]); // Map to button 0
});

InputGamepad.DeviceDisconnected.subscribe(gamepad => {
  console.log('Gamepad disconnected:', gamepad.name);
});

// Initialize detection
InputGamepad.Init();

Complete Example

import { Component, OnInit } from '@angular/core';
import { 
  InputService, 
  NavigationModule,
  KeyItemDirective,
  KeyItemGroupDirective,
  ActionBindingDirective 
} from '@workshack/input';

@Component({
  selector: 'app-game-menu',
  imports: [
    NavigationModule,
    KeyItemDirective, 
    KeyItemGroupDirective,
    ActionBindingDirective
  ],
  template: `
    <div class="game-menu" inputKeyItemGroup>
      <h2>Game Menu</h2>
      
      <button 
        inputKeyItem
        [actionBindings]="{
          Accept: () => startGame(),
          Back: () => goBack()
        }"
        (focused)="playHoverSound()"
        class="menu-item">
        Start Game
      </button>
      
      <button 
        inputKeyItem
        [actionBindings]="{
          Accept: () => showOptions(),
          Back: () => goBack()
        }"
        (focused)="playHoverSound()"
        class="menu-item">
        Options
      </button>
      
      <button 
        inputKeyItem
        [actionBindings]="{
          Accept: () => exitGame(),
          Back: () => goBack()
        }"
        (focused)="playHoverSound()"
        class="menu-item">
        Exit
      </button>
      
      <div class="controls-hint">
        <span>Use arrow keys or gamepad to navigate</span>
        <span>Press Enter or A to select</span>
      </div>
    </div>
  `,
  styles: [`
    .game-menu {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 1rem;
      padding: 2rem;
    }
    
    .menu-item {
      padding: 1rem 2rem;
      background: #333;
      color: white;
      border: 2px solid transparent;
      border-radius: 8px;
      cursor: pointer;
      transition: all 0.2s;
    }
    
    .menu-item.active,
    .menu-item:hover {
      background: #555;
      border-color: #007bff;
      transform: scale(1.05);
    }
    
    .controls-hint {
      display: flex;
      flex-direction: column;
      text-align: center;
      font-size: 0.8rem;
      color: #666;
      margin-top: 2rem;
    }
  `]
})
export class GameMenuComponent implements OnInit {
  
  constructor(private inputService: InputService) {}
  
  ngOnInit() {
    // Set up custom actions
    const menuAction = this.inputService.defineAction('menu');
    menuAction.onUp(() => this.toggleMenu());
    
    // Listen for device changes
    this.inputService.deviceListChanged.subscribe(devices => {
      console.log('Input devices:', devices.map(d => d.name));
    });
  }
  
  startGame() {
    console.log('Starting game...');
  }
  
  showOptions() {
    console.log('Showing options...');
  }
  
  exitGame() {
    console.log('Exiting game...');
  }
  
  goBack() {
    console.log('Going back...');
    return false; // Prevent default navigation
  }
  
  playHoverSound() {
    // Play hover sound effect
    console.log('Hover sound');
  }
  
  toggleMenu() {
    console.log('Toggle menu');
  }
}

Features

  • 🎮 Gamepad Support - Automatic detection and mapping for standard gamepads
  • ⌨️ Keyboard Navigation - Full keyboard navigation with customizable key bindings
  • 🖱️ Mouse/Touch Support - Works seamlessly with mouse and touch interactions
  • 🎯 Action Binding System - Flexible system for binding actions to inputs
  • 📐 Directional Navigation - Smart directional navigation based on element positions
  • 🎨 Customizable Themes - Support for different key name themes (PlayStation, Xbox, etc.)
  • 🔄 Hot-plugging - Automatic detection of connected/disconnected devices
  • 🎛️ Multiple Schemes - Support for multiple input schemes per device

Development

Building

npm run build:input

Watching for changes

npm run watch:input

Testing

npm run test:input

Publishing

npm run publish:input

License

This library is licensed under a custom license that allows:

  • Free use for non-commercial applications (no revenue, no ads, no paid features)
  • Commercial use with attribution requirement - you must credit the library in your app (e.g., on an "About" or "For Developers" page)

For full license terms, see the LICENSE file.

Attribution Example for Commercial Use

This application uses Workshack Input Library 
(https://www.npmjs.com/package/@workshack/input) 
© 2025 Workshack Team