Skip to content

Commit

Permalink
Create useKeyPress
Browse files Browse the repository at this point in the history
  • Loading branch information
littensy committed Apr 23, 2023
1 parent 7ef2e13 commit f8559c6
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from "./use-debounce-effect";
export * from "./use-debounce-state";
export * from "./use-event-listener";
export * from "./use-interval";
export * from "./use-key-press";
export * from "./use-latest";
export * from "./use-lifetime";
export * from "./use-memoized-callback";
Expand Down
40 changes: 40 additions & 0 deletions src/use-key-press/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## 🪝 `useKeyPress`

```ts
function useKeyPress(...keyCodes: KeyCodes[]): boolean;
```

Returns `true` if any of the given keys or shortcuts are pressed. The hook expects one or more key codes, which can be:

- A single key, like `"Space"`
- A combination of keys, like `"Space+W"`
- An array of keys, like `["Space", "W"]`

Each combination is treated as its own shortcut. If passed more than one key combination, the hook will return `true` if any of the combinations are pressed.

### 📕 Parameters

- `keyCodes` - One or more key codes.

### 📗 Returns

- Whether any of the given keys or shortcuts are pressed.

### 📘 Example

```tsx
function Keyboard() {
const spacePressed = useKeyPress("Space");
const ctrlAPressed = useKeyPress("LeftControl+A", "RightControl+A");

useEffect(() => {
print(`Space pressed: ${spacePressed}`);
}, [spacePressed]);

useEffect(() => {
print(`Ctrl+A pressed: ${ctrlAPressed}`);
}, [ctrlAPressed]);

return undefined!;
}
```
1 change: 1 addition & 0 deletions src/use-key-press/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./use-key-press";
13 changes: 13 additions & 0 deletions src/use-key-press/use-key-press.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference types="@rbxts/testez/globals" />

import { renderHook } from "../utils/testez";
import { useKeyPress } from "./use-key-press";

export = () => {
it("should return a boolean", () => {
const { result, unmount } = renderHook(() => useKeyPress("W"));
expect(result.current).to.be.a("boolean");
expect(result.current).to.equal(false);
unmount();
});
};
65 changes: 65 additions & 0 deletions src/use-key-press/use-key-press.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { InferEnumNames } from "@rbxts/roact";
import { useMemo, useState } from "@rbxts/roact-hooked";
import { UserInputService } from "@rbxts/services";
import { useEventListener } from "../use-event-listener";

/**
* A single key code name.
*/
export type KeyCode = InferEnumNames<Enum.KeyCode>;

/**
* A single key code or a combination of key codes.
*/
export type KeyCodes = KeyCode | `${KeyCode}+${string}` | KeyCode[];

/**
* Returns whether the passed key or shortcut is pressed. The hook expects one
* or more key code, which can be:
*
* - A single key code `"W"`
* - A combination of key codes `"W+Space"`
* - An array of key codes `["W", "Space"]`
*
* Each combination is treated as its own shortcut. If passed more than one
* combination, the hook will return `true` if any of the combinations are
* pressed.
*
* @param keyCodes The key code or combination of key codes to listen for.
* @returns Whether the key or combination of keys is pressed.
*/
export function useKeyPress(...keyCodes: KeyCodes[]) {
const [pressed, setPressed] = useState(false);

const keys = useMemo(() => {
if (typeIs(keyCodes, "string")) {
return keyCodes.split("+") as KeyCode[];
} else {
return keyCodes as KeyCode[];
}
}, keyCodes);

const keysDown = useMemo(() => {
return new Set<KeyCode>();
}, keyCodes);

const updatePressed = () => {
setPressed(keys.every((key) => keysDown.has(key)));
};

useEventListener(UserInputService.InputBegan, (input, gameProcessed) => {
if (!gameProcessed && keys.includes(input.KeyCode.Name)) {
keysDown.add(input.KeyCode.Name);
updatePressed();
}
});

useEventListener(UserInputService.InputEnded, (input) => {
if (keys.includes(input.KeyCode.Name)) {
keysDown.delete(input.KeyCode.Name);
updatePressed();
}
});

return pressed;
}

0 comments on commit f8559c6

Please sign in to comment.