three-gamepad-controls 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/gamepad-controls.d.ts +29 -0
- package/dist/gamepad-controls.js +35 -0
- package/dist/gamepad-haptics.js +88 -0
- package/dist/gamepad-input.d.ts +29 -0
- package/dist/gamepad-input.js +36 -0
- package/dist/gamepad-manager.js +8 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,7 @@ bun add -d @types/three # optional: for TypeScript projects
|
|
|
55
55
|
|
|
56
56
|
- [Core](./docs/core.md) — The fundamental building blocks.
|
|
57
57
|
- [GamepadInput](./docs/gamepad-input.md) - Low-level reader for gamepad buttons, axes, sticks, and transitions.
|
|
58
|
+
- [Haptic Feedback](./docs/haptic-feedback.md) - Optional gamepad vibration effects with graceful degradation.
|
|
58
59
|
- [Multiple Gamepads](./docs/multiple-gamepads.md) - Assign different gamepads to controls, players, and gameplay inputs.
|
|
59
60
|
- [GamepadControls](./docs/gamepad-controls.md) — Abstract base class for custom gamepad controls.
|
|
60
61
|
- [GamepadArcballControls](./docs/gamepad-arcball-controls.md) - Gamepad support for `ArcballControls`.
|
|
@@ -63,12 +63,41 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
|
|
|
63
63
|
* @returns The shared input reader for the active gamepad.
|
|
64
64
|
*/
|
|
65
65
|
protected get gamepadInput(): GamepadInput;
|
|
66
|
+
/**
|
|
67
|
+
* Whether the active gamepad exposes a callable primary vibration actuator.
|
|
68
|
+
*
|
|
69
|
+
* This does not guarantee support for every {@link GamepadHapticEffectType}.
|
|
70
|
+
*
|
|
71
|
+
* @returns `true` when vibration effects can be requested.
|
|
72
|
+
*/
|
|
73
|
+
get vibrationSupported(): boolean;
|
|
66
74
|
/**
|
|
67
75
|
* Advances the controller by one frame. Call this inside your render loop.
|
|
68
76
|
*
|
|
69
77
|
* @param deltaTime - Seconds since the last frame.
|
|
70
78
|
*/
|
|
71
79
|
update(deltaTime: number): void;
|
|
80
|
+
/**
|
|
81
|
+
* Plays an effect through the active gamepad's primary vibration actuator.
|
|
82
|
+
*
|
|
83
|
+
* Missing browser, gamepad, or effect support is treated as a safe no-op.
|
|
84
|
+
* Environmental failures such as a hidden document are also ignored.
|
|
85
|
+
* Invalid parameters and unexpected failures remain rejected.
|
|
86
|
+
*
|
|
87
|
+
* @param type - Haptic effect type to play.
|
|
88
|
+
* @param parameters - Optional parameters describing the effect.
|
|
89
|
+
* @returns The browser result, or `null` when the effect is ignored.
|
|
90
|
+
*/
|
|
91
|
+
playVibrationEffect(type: GamepadHapticEffectType, parameters?: GamepadEffectParameters): Promise<GamepadHapticsResult | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Stops the active effect on the gamepad's primary vibration actuator.
|
|
94
|
+
*
|
|
95
|
+
* Missing or temporarily unavailable haptics are treated as a safe no-op.
|
|
96
|
+
* Unexpected failures remain rejected.
|
|
97
|
+
*
|
|
98
|
+
* @returns The browser result, or `null` when reset is ignored.
|
|
99
|
+
*/
|
|
100
|
+
resetVibration(): Promise<GamepadHapticsResult | null>;
|
|
72
101
|
/**
|
|
73
102
|
* Removes all event listeners attached by this controller. Call when no longer needed.
|
|
74
103
|
*/
|
package/dist/gamepad-controls.js
CHANGED
|
@@ -44,6 +44,16 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
44
44
|
return this.#gamepadInput;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
+
* Whether the active gamepad exposes a callable primary vibration actuator.
|
|
48
|
+
*
|
|
49
|
+
* This does not guarantee support for every {@link GamepadHapticEffectType}.
|
|
50
|
+
*
|
|
51
|
+
* @returns `true` when vibration effects can be requested.
|
|
52
|
+
*/
|
|
53
|
+
get vibrationSupported() {
|
|
54
|
+
return this.#gamepadInput.vibrationSupported;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
47
57
|
* Forwards an input connection event to the overridable lifecycle hook.
|
|
48
58
|
*
|
|
49
59
|
* @param event - Input event containing the connected gamepad snapshot.
|
|
@@ -74,6 +84,31 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
74
84
|
this.onUpdate(deltaTime);
|
|
75
85
|
}
|
|
76
86
|
/**
|
|
87
|
+
* Plays an effect through the active gamepad's primary vibration actuator.
|
|
88
|
+
*
|
|
89
|
+
* Missing browser, gamepad, or effect support is treated as a safe no-op.
|
|
90
|
+
* Environmental failures such as a hidden document are also ignored.
|
|
91
|
+
* Invalid parameters and unexpected failures remain rejected.
|
|
92
|
+
*
|
|
93
|
+
* @param type - Haptic effect type to play.
|
|
94
|
+
* @param parameters - Optional parameters describing the effect.
|
|
95
|
+
* @returns The browser result, or `null` when the effect is ignored.
|
|
96
|
+
*/
|
|
97
|
+
playVibrationEffect(type, parameters) {
|
|
98
|
+
return this.#gamepadInput.playVibrationEffect(type, parameters);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Stops the active effect on the gamepad's primary vibration actuator.
|
|
102
|
+
*
|
|
103
|
+
* Missing or temporarily unavailable haptics are treated as a safe no-op.
|
|
104
|
+
* Unexpected failures remain rejected.
|
|
105
|
+
*
|
|
106
|
+
* @returns The browser result, or `null` when reset is ignored.
|
|
107
|
+
*/
|
|
108
|
+
resetVibration() {
|
|
109
|
+
return this.#gamepadInput.resetVibration();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
77
112
|
* Removes all event listeners attached by this controller. Call when no longer needed.
|
|
78
113
|
*/
|
|
79
114
|
dispose() {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/gamepad-haptics.ts
|
|
2
|
+
/**
|
|
3
|
+
* Returns the primary vibration actuator exposed by a gamepad at runtime.
|
|
4
|
+
*
|
|
5
|
+
* The DOM types follow the specification and declare `vibrationActuator` as
|
|
6
|
+
* always present, while browsers without haptic support may omit it entirely.
|
|
7
|
+
* Access failures are treated as lack of support.
|
|
8
|
+
*
|
|
9
|
+
* @param gamepad - Active gamepad snapshot, or `null`.
|
|
10
|
+
* @returns The runtime actuator, or `null` when unavailable.
|
|
11
|
+
*/
|
|
12
|
+
const getGamepadVibrationActuator = (gamepad) => {
|
|
13
|
+
if (gamepad === null) return null;
|
|
14
|
+
try {
|
|
15
|
+
return gamepad.vibrationActuator ?? null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Identifies environmental haptics failures that should degrade to a no-op.
|
|
22
|
+
*
|
|
23
|
+
* `NotSupportedError` means the actuator cannot play the requested effect.
|
|
24
|
+
* `InvalidStateError` means the document cannot currently issue the effect,
|
|
25
|
+
* such as while it is hidden. Parameter errors intentionally remain visible.
|
|
26
|
+
*
|
|
27
|
+
* @param error - Rejection reason returned by the browser.
|
|
28
|
+
* @returns Whether the failure should be ignored as unavailable haptics.
|
|
29
|
+
*/
|
|
30
|
+
const isIgnorableHapticsError = (error) => {
|
|
31
|
+
if (typeof error !== "object" || error === null || !("name" in error)) return false;
|
|
32
|
+
const { name } = error;
|
|
33
|
+
return name === "NotSupportedError" || name === "InvalidStateError";
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Returns whether the active gamepad exposes the current vibration API.
|
|
37
|
+
*
|
|
38
|
+
* This detects actuator and method availability, not support for a particular
|
|
39
|
+
* haptic effect type.
|
|
40
|
+
*
|
|
41
|
+
* @param gamepad - Active gamepad snapshot, or `null`.
|
|
42
|
+
* @returns `true` when `vibrationActuator.playEffect` is callable.
|
|
43
|
+
*/
|
|
44
|
+
const isGamepadVibrationSupported = (gamepad) => {
|
|
45
|
+
return typeof getGamepadVibrationActuator(gamepad)?.playEffect === "function";
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Plays a haptic effect through the current primary vibration actuator.
|
|
49
|
+
*
|
|
50
|
+
* Unsupported or temporarily unavailable haptics resolve to `null`. Invalid
|
|
51
|
+
* parameters and other unexpected failures remain rejected.
|
|
52
|
+
*
|
|
53
|
+
* @param gamepad - Active gamepad snapshot, or `null`.
|
|
54
|
+
* @param type - Haptic effect type to play.
|
|
55
|
+
* @param parameters - Optional parameters describing the effect.
|
|
56
|
+
* @returns The browser result, or `null` when the effect is ignored.
|
|
57
|
+
*/
|
|
58
|
+
const playGamepadVibrationEffect = async (gamepad, type, parameters) => {
|
|
59
|
+
const actuator = getGamepadVibrationActuator(gamepad);
|
|
60
|
+
if (typeof actuator?.playEffect !== "function") return null;
|
|
61
|
+
try {
|
|
62
|
+
return await actuator.playEffect(type, parameters);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (isIgnorableHapticsError(error)) return null;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Stops the active effect on the current primary vibration actuator.
|
|
70
|
+
*
|
|
71
|
+
* Unsupported or temporarily unavailable haptics resolve to `null`.
|
|
72
|
+
* Unexpected failures remain rejected.
|
|
73
|
+
*
|
|
74
|
+
* @param gamepad - Active gamepad snapshot, or `null`.
|
|
75
|
+
* @returns The browser result, or `null` when reset is ignored.
|
|
76
|
+
*/
|
|
77
|
+
const resetGamepadVibration = async (gamepad) => {
|
|
78
|
+
const actuator = getGamepadVibrationActuator(gamepad);
|
|
79
|
+
if (typeof actuator?.reset !== "function") return null;
|
|
80
|
+
try {
|
|
81
|
+
return await actuator.reset();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (isIgnorableHapticsError(error)) return null;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
export { isGamepadVibrationSupported, playGamepadVibrationEffect, resetGamepadVibration };
|
package/dist/gamepad-input.d.ts
CHANGED
|
@@ -109,6 +109,14 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
|
|
|
109
109
|
* @returns The raw active gamepad snapshot, or `null`.
|
|
110
110
|
*/
|
|
111
111
|
get rawGamepad(): Gamepad | null;
|
|
112
|
+
/**
|
|
113
|
+
* Whether the active gamepad exposes a callable primary vibration actuator.
|
|
114
|
+
*
|
|
115
|
+
* This does not guarantee support for every {@link GamepadHapticEffectType}.
|
|
116
|
+
*
|
|
117
|
+
* @returns `true` when vibration effects can be requested.
|
|
118
|
+
*/
|
|
119
|
+
get vibrationSupported(): boolean;
|
|
112
120
|
/**
|
|
113
121
|
* Polls the gamepad and refreshes current and previous button state.
|
|
114
122
|
*/
|
|
@@ -162,6 +170,27 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
|
|
|
162
170
|
* @returns Object containing processed `x` and `y` values.
|
|
163
171
|
*/
|
|
164
172
|
stick(xAxis: number, yAxis: number, options?: GamepadAxisOptions): GamepadStick;
|
|
173
|
+
/**
|
|
174
|
+
* Plays an effect through the active gamepad's primary vibration actuator.
|
|
175
|
+
*
|
|
176
|
+
* Missing browser, gamepad, or effect support is treated as a safe no-op.
|
|
177
|
+
* Environmental failures such as a hidden document are also ignored.
|
|
178
|
+
* Invalid parameters and unexpected failures remain rejected.
|
|
179
|
+
*
|
|
180
|
+
* @param type - Haptic effect type to play.
|
|
181
|
+
* @param parameters - Optional parameters describing the effect.
|
|
182
|
+
* @returns The browser result, or `null` when the effect is ignored.
|
|
183
|
+
*/
|
|
184
|
+
playVibrationEffect(type: GamepadHapticEffectType, parameters?: GamepadEffectParameters): Promise<GamepadHapticsResult | null>;
|
|
185
|
+
/**
|
|
186
|
+
* Stops the active effect on the gamepad's primary vibration actuator.
|
|
187
|
+
*
|
|
188
|
+
* Missing or temporarily unavailable haptics are treated as a safe no-op.
|
|
189
|
+
* Unexpected failures remain rejected.
|
|
190
|
+
*
|
|
191
|
+
* @returns The browser result, or `null` when reset is ignored.
|
|
192
|
+
*/
|
|
193
|
+
resetVibration(): Promise<GamepadHapticsResult | null>;
|
|
165
194
|
}
|
|
166
195
|
//#endregion
|
|
167
196
|
export { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick };
|
package/dist/gamepad-input.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isGamepadVibrationSupported, playGamepadVibrationEffect, resetGamepadVibration } from "./gamepad-haptics.js";
|
|
1
2
|
import { GamepadManager } from "./gamepad-manager.js";
|
|
2
3
|
import { EventDispatcher } from "three";
|
|
3
4
|
//#region src/gamepad-input.ts
|
|
@@ -131,6 +132,16 @@ var GamepadInput = class extends EventDispatcher {
|
|
|
131
132
|
return this.#gamepad;
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
135
|
+
* Whether the active gamepad exposes a callable primary vibration actuator.
|
|
136
|
+
*
|
|
137
|
+
* This does not guarantee support for every {@link GamepadHapticEffectType}.
|
|
138
|
+
*
|
|
139
|
+
* @returns `true` when vibration effects can be requested.
|
|
140
|
+
*/
|
|
141
|
+
get vibrationSupported() {
|
|
142
|
+
return isGamepadVibrationSupported(this.#gamepad);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
134
145
|
* Polls the gamepad and refreshes current and previous button state.
|
|
135
146
|
*/
|
|
136
147
|
update() {
|
|
@@ -230,6 +241,31 @@ var GamepadInput = class extends EventDispatcher {
|
|
|
230
241
|
};
|
|
231
242
|
}
|
|
232
243
|
/**
|
|
244
|
+
* Plays an effect through the active gamepad's primary vibration actuator.
|
|
245
|
+
*
|
|
246
|
+
* Missing browser, gamepad, or effect support is treated as a safe no-op.
|
|
247
|
+
* Environmental failures such as a hidden document are also ignored.
|
|
248
|
+
* Invalid parameters and unexpected failures remain rejected.
|
|
249
|
+
*
|
|
250
|
+
* @param type - Haptic effect type to play.
|
|
251
|
+
* @param parameters - Optional parameters describing the effect.
|
|
252
|
+
* @returns The browser result, or `null` when the effect is ignored.
|
|
253
|
+
*/
|
|
254
|
+
playVibrationEffect(type, parameters) {
|
|
255
|
+
return playGamepadVibrationEffect(this.#gamepad, type, parameters);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Stops the active effect on the gamepad's primary vibration actuator.
|
|
259
|
+
*
|
|
260
|
+
* Missing or temporarily unavailable haptics are treated as a safe no-op.
|
|
261
|
+
* Unexpected failures remain rejected.
|
|
262
|
+
*
|
|
263
|
+
* @returns The browser result, or `null` when reset is ignored.
|
|
264
|
+
*/
|
|
265
|
+
resetVibration() {
|
|
266
|
+
return resetGamepadVibration(this.#gamepad);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
233
269
|
* Handles a browser connection event and adopts the gamepad when possible.
|
|
234
270
|
*
|
|
235
271
|
* Button state is seeded as both current and previous so an already-held
|
package/dist/gamepad-manager.js
CHANGED
|
@@ -47,18 +47,19 @@ var GamepadManager = class {
|
|
|
47
47
|
return selectedGamepad;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Clears the active gamepad when
|
|
50
|
+
* Clears the active gamepad when its browser-assigned index matches the
|
|
51
|
+
* disconnecting gamepad.
|
|
51
52
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* Gamepad API snapshots are not guaranteed to preserve JavaScript object
|
|
54
|
+
* identity between events and polls, so the active device is correlated by
|
|
55
|
+
* its logical slot. A matching disconnection defers any replacement until
|
|
56
|
+
* the next update.
|
|
56
57
|
*
|
|
57
58
|
* @param gamepad - Gamepad snapshot that disconnected.
|
|
58
59
|
* @returns The previously active gamepad when it was cleared, otherwise `null`.
|
|
59
60
|
*/
|
|
60
61
|
disconnect(gamepad) {
|
|
61
|
-
if (this.activeGamepad !== gamepad) return null;
|
|
62
|
+
if (this.activeGamepad === null || this.activeGamepad.index !== gamepad.index) return null;
|
|
62
63
|
const disconnectedGamepad = this.activeGamepad;
|
|
63
64
|
this.activeGamepad = null;
|
|
64
65
|
this.#connectionDeferredUntilUpdate = true;
|
|
@@ -86,7 +87,7 @@ var GamepadManager = class {
|
|
|
86
87
|
}
|
|
87
88
|
const previousGamepad = this.activeGamepad;
|
|
88
89
|
const nextGamepad = this.#getGamepadByIndex(previousGamepad.index);
|
|
89
|
-
if (
|
|
90
|
+
if (nextGamepad === null) {
|
|
90
91
|
this.activeGamepad = null;
|
|
91
92
|
this.#connectionDeferredUntilUpdate = true;
|
|
92
93
|
return {
|
package/package.json
CHANGED