npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-control

v0.2.1

Published

A powerful set of hooks for elegantly handling keyboard, mouse and gamepad input (soon)

Downloads

35

Readme

Version Twitter ETH Language License Bundle Size Build

Installation

npm i use-control
yarn add use-control

Example

First, we set up an input mapping. Inputs come in two flavours:

  • buttons: discrete inputs like keyboard presses, mouse clicks and gamepad buttons
  • axes: continuous inputs like mouse position, gamepad joysticks and triggers
const inputMap = {
  buttons: {
    left: [
      keycode(KEYS.left_arrow),
      mouseButton('left'),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_LEFT),
    ],
    right: [
      keycode(KEYS.right_arrow),
      mouseButton('right'),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_RIGHT),
    ],
  },
  axes: {
    x: [mouseAxis('x'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_X)],
    y: [mouseAxis('y'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_Y)],
  },
}

Then we can wire up our input listeners within a component using various hooks.

const MyComponent = () => {
  const [count, setCount] = useState(0)

  useButtonPressed(inputMap, "left", () => {
    setCount(count - 1)
  })

  useButtonPressed(inputMap, "right", () => {
    setCount(count + 1)
  })

  useAxis(inputMap, "x", v => {
    console.log("x-axis", v)
  })

  return <div>{count}</div>
}

Check out the full example for more details.

Note: if you want to use gamepad as an input source you need to call gamepadInit() in the entry point of your app to set up the listeners

API Overview

Bootstrap

If you want to use gamepad input you'll need to attach the listeners when your app starts up, this probably means you want to call gamepadInit once in index.js or App.js but you can turn the feature on and off at your leisure.

  • gamepadInit()
  • gamepadTeardown()

Hooks

  • useButtonPressed(inputMap, actionName, callback)
  • useButtonReleased(inputMap, actionName, callback)
  • useButtonHeld(inputMap, actionName, throttleInterval, callback)
  • useAxis(inputMap, axisName, callback)

Input Sources

These functions can be use to construct bindings for input maps:

  • mouseButton('left' | 'right' | 'middle')
  • mouseAxis('x' | 'y')
  • keycode(code)
  • gamepadButton(controllerIndex, buttonIndex)
  • gamepadAxis(controllerIndex, buttonIndex)

Primitive Hooks

If you need to dig down and specifically target one form of input it might be more useful to pick from this list:

import { useKeyDown, useKeyUp, useKeyHeld } from 'use-control/lib/input/keyboard'

  • useKeyDown(keyCode, callback)
  • useKeyUp(keyCode, callback)
  • useKeyHeld(keyCode, callback)

import { useMouseMove, useMouseMoveNormalised, useMouseDelta } from 'use-control/lib/input/keyboard'

  • useMouseMove(callback)
  • useMouseMoveNormalised(callback)
  • useMouseDelta(callback)

import { useGamepadButtonPressed, useGamepadAxis } from 'use-control/lib/input/keyboard'

  • useGamepadButtonPressed(controllerIndex, buttonIndex, callback)
  • useGamepadAxis(controllerIndex, axisIndex, callback)

Roadmap

  • Virtual joystick support
  • Accelerometer input support
  • Controller button mappings for
    • PS4
    • Xbox 360
    • Xbox One
    • (and any others contributed)

Why use-control?

Personally, I'm just tired of writing useEffect with document.addEventListener('keydown', ...).

use-control is the API I've always dreamed of for dealing with input events, it's heavily inspired by my experience with input systems in game development. It's a tiny, batteries-included library for focusing on the actual user interactions rather than boilerplate and ochestration.

Usage

use-control relies on the core concept of an Input Mapping of keycodes, mouse buttons and gamepad buttons into Input Actions (i.e. "left", "right", "jump", "select"), declared as a JS object:

const inputMap = {
  buttons: {
    left: [
      keycode(KEYS.left_arrow),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_LEFT),
    ],
    right: [
      keycode(KEYS.right_arrow),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_RIGHT),
    ],
    jump: [
      keycode(KEYS.space)
    ]
  },
  axes: {
    x: [mouseAxis('x'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_X)],
    y: [mouseAxis('y'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_Y)],
  },
}

You should consider declaring this statically and sharing the mapping across your app but it can be dynamically updated at runtime and different mappings can be used in different components as needed.

These mappings allow us to think at a higher level when consuming input, instead of asking "what events do I need to bind to?" or "what keycode am I listening for?" we can simply ask "what happens when the user presses the jump button?"

useButtonPressed(inputMap, "jump", () => {
  player.addForce(0, -10)
})

Running this repo

Bootstrap

yarn
yarn bootstrap

Running the examples

cd packages/use-control
yarn build
cd ../example
yarn start