Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,49 @@ Helper library for Gamepad API. Helps with detecting type of gamepad and mapping
npm install @lizardbyte/gamepad-helper --ignore-scripts
```

## Controller visuals

`GamepadHelper` can render the packaged Xbox, PlayStation, and Nintendo Switch controller artwork and keep its buttons,
triggers, and sticks synchronized with a browser `Gamepad` object. The consumer controls the asset location, color scheme,
layout, and theme styling.

```js
const gamepadHelper = new GamepadHelper();
const visualizer = gamepadHelper.createVisualizer(
document.getElementById('controller-visual'),
{
assetBasePath: '/assets/img/gamepads/',
colorScheme: 'White',
},
);

visualizer.mount(gamepad);
visualizer.update(navigator.getGamepads()[gamepad.index]);

// Remount the current controller with light-theme artwork.
visualizer.setColorScheme('Black');

// Remove the generated DOM when the visual is no longer needed.
visualizer.destroy();
```

The renderer emits stable `gamepad-visual-*`, `gamepad-trigger-*`, and `gamepad-stick-indicator` classes for consumer
styles. Custom renderers can use `getControllerVisualConfig()` and `getControllerImagePath()` without duplicating the
asset-relative metadata.

## Compatibility issues

Use `getCompatibilityIssues()` to keep browser/controller compatibility knowledge in the library while presenting the
warning in the consumer's own UI.

```js
const issues = gamepadHelper.getCompatibilityIssues(gamepad);

issues.forEach(issue => {
console.warn(issue.message, issue.issueUrl);
});
```

## Attribution

- Button Icons and Controls were created by Zacksly (Licensed under CC BY 3.0 - https://zacksly.itch.io)
94 changes: 94 additions & 0 deletions src/js/gamepad-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
* This module provides a set of utilities for working with gamepads in web applications.
*/

const {
GamepadVisualizer,
getControllerImagePath,
getControllerVisualConfig,
} = require('./gamepad-visualizer');

const FIREFOX_SWITCH_GAMEPAD_FIXED_VERSION = 155;
const FIREFOX_SWITCH_GAMEPAD_ISSUE_URL = 'https://bugzilla.mozilla.org/show_bug.cgi?id=1704419';

/**
* Controller identity metadata used by browser ID lookups.
* @typedef {Object} GamepadIdentityMapping
Expand Down Expand Up @@ -372,6 +381,40 @@ class GamepadHelper {
return null;
}

/**
* Get the image path for a full controller visual.
* @param {string} controllerType - The type of controller (XBOX, PLAYSTATION, SWITCH)
* @param {string} [basePath='/assets/img/gamepads/'] - The base path for the images
* @param {string} [colorScheme='White'] - The image color ('Black' or 'White')
* @returns {string|null} Encoded controller image path, or null when unavailable
*/
getControllerImagePath(
controllerType,
basePath = '/assets/img/gamepads/',
colorScheme = 'White',
) {
return getControllerImagePath(controllerType, basePath, colorScheme);
}

/**
* Get a copy of the visual definition for a controller type.
* @param {string} controllerType - The type of controller (XBOX, PLAYSTATION, SWITCH)
* @returns {Object|null} Controller visual definition, or null when unavailable
*/
getControllerVisualConfig(controllerType) {
return getControllerVisualConfig(controllerType);
}

/**
* Create a reusable visualizer in a caller-provided DOM container.
* @param {Element} container - DOM element that will contain the visual
* @param {Object} [options] - Visualizer options
* @returns {GamepadVisualizer} Controller visualizer
*/
createVisualizer(container, options = {}) {
return new GamepadVisualizer(this, container, options);
}

/**
* Check if the Gamepad API is supported in the current browser
* @returns {boolean} True if supported, false otherwise
Expand Down Expand Up @@ -449,6 +492,55 @@ class GamepadHelper {
return this.getGamepadInfo(gamepadId).type;
}

/**
* Extract the major Firefox version from a user-agent string.
* @param {string|null} userAgent - Browser user-agent string
* @returns {number|null} Firefox major version, or null for other browsers
*/
getFirefoxMajorVersion(userAgent) {
if (typeof userAgent !== 'string') {
return null;
}

const match = /\bFirefox\/(\d+)/.exec(userAgent);
return match ? Number.parseInt(match[1], 10) : null;
}

/**
* Get known browser/controller compatibility issues for a gamepad.
* @param {Gamepad|null} gamepad - Gamepad to inspect
* @param {Object} [options] - Detection options
* @param {string} [options.userAgent=navigator.userAgent] - Browser user-agent string
* @returns {Object[]} Structured compatibility issues
*/
getCompatibilityIssues(gamepad, options = {}) {
if (!gamepad) {
return [];
}

const userAgent = options.userAgent ?? globalThis.navigator?.userAgent ?? '';
const firefoxVersion = this.getFirefoxMajorVersion(userAgent);
const controllerInfo = this.getGamepadInfo(gamepad.id);
if (
firefoxVersion === null
|| firefoxVersion >= FIREFOX_SWITCH_GAMEPAD_FIXED_VERSION
|| controllerInfo.type !== this.CONTROLLER_TYPES.SWITCH
) {
return [];
}

return [{
code: 'firefox-switch-gamepad-mapping',
severity: 'warning',
browser: 'firefox',
browserVersion: firefoxVersion,
controllerType: controllerInfo.type,
fixedVersion: FIREFOX_SWITCH_GAMEPAD_FIXED_VERSION,
issueUrl: FIREFOX_SWITCH_GAMEPAD_ISSUE_URL,
message: `Firefox versions before ${FIREFOX_SWITCH_GAMEPAD_FIXED_VERSION} can report incorrect buttons and axes for Nintendo Switch controllers.`,
}];
}

/**
* Get button name for given controller type and button index
* @param {string} controllerType - The type of controller (XBOX, PLAYSTATION, SWITCH, STANDARD)
Expand Down Expand Up @@ -590,5 +682,7 @@ if (globalThis.window) {
globalThis.GamepadHelper = GamepadHelper;
}

GamepadHelper.GamepadVisualizer = GamepadVisualizer;

// Export the GamepadHelper class
module.exports = GamepadHelper;
Loading