three-gamepad-controls 0.23.0 โ†’ 0.24.1

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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2026 Lucas Alves Costa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -25,8 +25,8 @@ npm i -D @types/three # optional: for TypeScript projects
25
25
  pnpm:
26
26
 
27
27
  ```bash
28
- pnpm add three three-gamepad-controls
29
- pnpm add -D @types/three # optional: for TypeScript projects
28
+ pn add three three-gamepad-controls
29
+ pn add -D @types/three # optional: for TypeScript projects
30
30
  ```
31
31
 
32
32
  Yarn:
@@ -58,12 +58,41 @@ bun add three three-gamepad-controls
58
58
  bun add -d @types/three # optional: for TypeScript projects
59
59
  ```
60
60
 
61
+ ## ๐Ÿงช Testing
62
+
63
+ Tests use Vitest with Browser Mode and Playwright for browser-dependent behavior in Chromium. Browser-independent tests run directly in Node.js.
64
+
65
+ After installing the project dependencies, install Chromium once:
66
+
67
+ ```bash
68
+ pn exec playwright install chromium
69
+ ```
70
+
71
+ Run the test suite once:
72
+
73
+ ```bash
74
+ pn test
75
+ ```
76
+
77
+ Start Vitest in watch mode while developing:
78
+
79
+ ```bash
80
+ pn test:watch
81
+ ```
82
+
83
+ Generate the coverage report:
84
+
85
+ ```bash
86
+ pn test:coverage
87
+ ```
88
+
61
89
  ## ๐Ÿ”— Compatibility
62
90
 
63
91
  | Three.js | Three.js Gamepad Controls |
64
92
  | --- | --- |
65
- | `~0.184.0` | `<=0.22.0` |
66
- | `~0.185.0` | `0.23.0` |
93
+ | `0.184.x` | `<=0.22.x` |
94
+ | `0.185.x` | `0.23.x` |
95
+ | `0.186.x` | `0.24.x` |
67
96
 
68
97
  ## ๐Ÿ“– Documentation
69
98
 
@@ -85,4 +114,4 @@ bun add -d @types/three # optional: for TypeScript projects
85
114
 
86
115
  ## ๐Ÿ“„ License
87
116
 
88
- Licensed under the [MIT License](./LICENSE).
117
+ Licensed under the [MIT License](./LICENSE.md).
package/dist/core.d.ts CHANGED
@@ -4,19 +4,19 @@
4
4
  *
5
5
  * Gamepad slots cannot be negative.
6
6
  */
7
- declare const MIN_GAMEPAD_INDEX = 0;
7
+ export declare const MIN_GAMEPAD_INDEX = 0;
8
8
  /**
9
9
  * Largest valid browser-assigned gamepad index.
10
10
  *
11
11
  * `Gamepad.index` is a Web IDL `long`, a signed 32-bit integer. Because
12
12
  * gamepad slots cannot be negative, the largest valid index is 2^31 - 1.
13
13
  */
14
- declare const MAX_GAMEPAD_INDEX = 2147483647;
14
+ export declare const MAX_GAMEPAD_INDEX = 2147483647;
15
15
  /**
16
16
  * Button indices for the W3C Standard Gamepad mapping.
17
17
  * @see https://www.w3.org/TR/gamepad/#dfn-standard-gamepad
18
18
  */
19
- declare const GAMEPAD_BUTTON: {
19
+ export declare const GAMEPAD_BUTTON: {
20
20
  readonly South: 0;
21
21
  readonly East: 1;
22
22
  readonly West: 2;
@@ -39,7 +39,7 @@ declare const GAMEPAD_BUTTON: {
39
39
  * Axis indices for the W3C Standard Gamepad mapping.
40
40
  * @see https://www.w3.org/TR/gamepad/#dfn-standard-gamepad
41
41
  */
42
- declare const GAMEPAD_AXIS: {
42
+ export declare const GAMEPAD_AXIS: {
43
43
  readonly LeftX: 0;
44
44
  readonly LeftY: 1;
45
45
  readonly RightX: 2;
@@ -48,18 +48,17 @@ declare const GAMEPAD_AXIS: {
48
48
  /**
49
49
  * Union type of all valid {@link GAMEPAD_BUTTON} keys.
50
50
  */
51
- type GamepadButtonKey = keyof typeof GAMEPAD_BUTTON;
51
+ export type GamepadButtonKey = keyof typeof GAMEPAD_BUTTON;
52
52
  /**
53
53
  * Union type of all valid {@link GAMEPAD_BUTTON} values.
54
54
  */
55
- type GamepadButtonValue = (typeof GAMEPAD_BUTTON)[keyof typeof GAMEPAD_BUTTON];
55
+ export type GamepadButtonValue = (typeof GAMEPAD_BUTTON)[keyof typeof GAMEPAD_BUTTON];
56
56
  /**
57
57
  * Union type of all valid {@link GAMEPAD_AXIS} keys.
58
58
  */
59
- type GamepadAxisKey = keyof typeof GAMEPAD_AXIS;
59
+ export type GamepadAxisKey = keyof typeof GAMEPAD_AXIS;
60
60
  /**
61
61
  * Union type of all valid {@link GAMEPAD_AXIS} values.
62
62
  */
63
- type GamepadAxisValue = (typeof GAMEPAD_AXIS)[keyof typeof GAMEPAD_AXIS];
64
- //#endregion
65
- export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadAxisKey, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue, MAX_GAMEPAD_INDEX, MIN_GAMEPAD_INDEX };
63
+ export type GamepadAxisValue = (typeof GAMEPAD_AXIS)[keyof typeof GAMEPAD_AXIS];
64
+ //#endregion
@@ -7,7 +7,7 @@ import { ArcballControls } from "three/addons/controls/ArcballControls.js";
7
7
  *
8
8
  * Every property has a sensible default, so you only need to pass the properties you want to override.
9
9
  */
10
- type GamepadArcballControlsOptions = GamepadControlsOptions & {
10
+ export type GamepadArcballControlsOptions = GamepadControlsOptions & {
11
11
  /**
12
12
  * Multiplier on `ArcballControls.rotateSpeed` for rotation.
13
13
  * @default 1.0
@@ -76,7 +76,7 @@ type GamepadArcballControlsOptions = GamepadControlsOptions & {
76
76
  * Arcball transformations. The wrapped `ArcballControls.update()` is only
77
77
  * needed after manual camera or target changes, matching Arcball's native API.
78
78
  */
79
- declare class GamepadArcballControls extends GamepadControls {
79
+ export declare class GamepadArcballControls extends GamepadControls {
80
80
  #private;
81
81
  /**
82
82
  * @param controls - A Three.js `ArcballControls` instance.
@@ -91,6 +91,7 @@ declare class GamepadArcballControls extends GamepadControls {
91
91
  * @param deltaTime - Seconds since the last frame.
92
92
  */
93
93
  protected onUpdate(deltaTime: number): void;
94
+ dispose(): void;
95
+ protected onGamepadDisconnected(gamepad: Gamepad): void;
94
96
  }
95
- //#endregion
96
- export { GamepadArcballControls, GamepadArcballControlsOptions };
97
+ //#endregion
@@ -77,10 +77,7 @@ var GamepadArcballControls = class extends GamepadControls {
77
77
  const { rotateSpeed, panSpeed, zoomSpeed, zRotateSpeed, rotateStick, panStick, buttonDeadzone, buttonZoomIn, buttonZoomOut, buttonZRotateLeft, buttonZRotateRight, buttonFocus } = this.#options;
78
78
  const input = this.gamepadInput;
79
79
  const focusPoint = this.#consumeFocusPoint(buttonFocus);
80
- if (!controls.enabled) {
81
- this.#endInteraction();
82
- return;
83
- }
80
+ if (!this.#canApplyInput()) return;
84
81
  let rotateX = 0;
85
82
  let rotateY = 0;
86
83
  if (controls.enableRotate) {
@@ -102,20 +99,38 @@ var GamepadArcballControls = class extends GamepadControls {
102
99
  this.#endInteraction();
103
100
  return;
104
101
  }
105
- if (!this.#wasInteracting) controls.dispatchEvent({ type: "start" });
102
+ if (!this.#wasInteracting) {
103
+ this.#wasInteracting = true;
104
+ controls.dispatchEvent({ type: "start" });
105
+ }
106
+ if (!this.#canApplyInput()) return;
106
107
  let changed = false;
107
- changed = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed) || changed;
108
- changed = this.#applyPan(deltaTime, panX, panY, panSpeed) || changed;
109
- changed = this.#applyZoom(deltaTime, zoom, zoomSpeed, buttonDeadzone) || changed;
110
- changed = this.#applyZRotation(deltaTime, zRotation, zRotateSpeed, buttonDeadzone) || changed;
111
- changed = this.#applyFocus(focusPoint) || changed;
108
+ if (controls.enableRotate) changed = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed) || changed;
109
+ if (controls.enablePan) changed = this.#applyPan(deltaTime, panX, panY, panSpeed) || changed;
110
+ if (controls.enableZoom) changed = this.#applyZoom(deltaTime, zoom, zoomSpeed, buttonDeadzone) || changed;
111
+ if (controls.enableRotate) changed = this.#applyZRotation(deltaTime, zRotation, zRotateSpeed, buttonDeadzone) || changed;
112
+ if (controls.enablePan && controls.enableFocus) changed = this.#applyFocus(focusPoint) || changed;
112
113
  if (changed) {
113
114
  controls.update();
114
115
  controls.updateMatrixState();
115
116
  controls.dispatchEvent({ type: "change" });
116
117
  }
117
- this.#wasInteracting = activeInput;
118
- if (!activeInput) this.#endInteraction();
118
+ if (!activeInput || !controls.enabled) this.#endInteraction();
119
+ }
120
+ dispose() {
121
+ super.dispose();
122
+ this.#endInteraction();
123
+ }
124
+ onGamepadDisconnected(gamepad) {
125
+ this.#endInteraction();
126
+ super.onGamepadDisconnected(gamepad);
127
+ }
128
+ #canApplyInput() {
129
+ if (!this.#controls.enabled) {
130
+ this.#endInteraction();
131
+ return false;
132
+ }
133
+ return this.enabled && this.gamepad !== null;
119
134
  }
120
135
  /**
121
136
  * Applies gamepad stick rotation through Arcball's runtime rotation helper.
@@ -253,8 +268,8 @@ var GamepadArcballControls = class extends GamepadControls {
253
268
  }
254
269
  #endInteraction() {
255
270
  if (!this.#wasInteracting) return;
256
- this.#controls.dispatchEvent({ type: "end" });
257
271
  this.#wasInteracting = false;
272
+ this.#controls.dispatchEvent({ type: "end" });
258
273
  }
259
274
  };
260
275
  //#endregion
@@ -7,7 +7,7 @@ import { EventDispatcher } from "three";
7
7
  * Each key is an event name, and its value is the extra data included in the event object
8
8
  * alongside the standard `type` and `target` fields.
9
9
  */
10
- type GamepadControlsEventMap = {
10
+ export type GamepadControlsEventMap = {
11
11
  /**
12
12
  * Fired when a gamepad is connected and set as the active gamepad.
13
13
  */
@@ -18,7 +18,8 @@ type GamepadControlsEventMap = {
18
18
  gamepad: Gamepad;
19
19
  };
20
20
  /**
21
- * Fired when the active gamepad is disconnected or replaced in its slot.
21
+ * Fired on a matching browser disconnection event or when polling observes
22
+ * the active slot missing or disconnected, not on snapshot identity changes.
22
23
  */
23
24
  disconnected: {
24
25
  /**
@@ -30,22 +31,25 @@ type GamepadControlsEventMap = {
30
31
  /**
31
32
  * Shared configuration for Three.js gamepad control wrappers.
32
33
  */
33
- type GamepadControlsOptions = Pick<GamepadInputOptions, "gamepadIndex">;
34
+ export type GamepadControlsOptions = Pick<GamepadInputOptions, "gamepadIndex">;
34
35
  /**
35
36
  * Abstract base class for Three.js gamepad controls.
36
37
  *
37
38
  * Delegates gamepad connection lifecycle and input polling to {@link GamepadInput}
38
39
  * so subclasses can focus on mapping input to the wrapped Three.js control.
39
40
  */
40
- declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEventMap> {
41
+ export declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEventMap> {
41
42
  #private;
42
43
  /**
43
- * When `false`, all input processing is paused.
44
+ * When `false`, `update()` pauses polling and subclass input application,
45
+ * retaining state. Browser connection/disconnection listeners remain active.
46
+ * Assignment alone does not cancel an interaction or dispose the wrapper.
44
47
  * @default true
45
48
  */
46
49
  enabled: boolean;
47
50
  /**
48
- * The currently active gamepad, or `null` if no gamepad is connected.
51
+ * The active gamepad snapshot, or `null` if none has been adopted.
52
+ * Automatic selection keeps its adopted slot until an observed loss.
49
53
  */
50
54
  gamepad: Gamepad | null;
51
55
  /**
@@ -72,6 +76,8 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
72
76
  get vibrationSupported(): boolean;
73
77
  /**
74
78
  * Advances the controller by one frame. Call this inside your render loop.
79
+ * While paused, polling-only losses remain unobserved until updates resume.
80
+ * Resume compares button state with the last observation, without replay.
75
81
  *
76
82
  * @param deltaTime - Seconds since the last frame.
77
83
  */
@@ -98,7 +104,9 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
98
104
  */
99
105
  resetVibration(): Promise<GamepadHapticsResult | null>;
100
106
  /**
101
- * Removes all event listeners attached by this controller. Call when no longer needed.
107
+ * Removes this wrapper's internal input listeners, clears state, and disables
108
+ * it without dispatching a synthetic disconnection. Does not dispose the
109
+ * wrapped Three.js control or affect other wrappers. Reuse is unsupported.
102
110
  */
103
111
  dispose(): void;
104
112
  /**
@@ -116,8 +124,8 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
116
124
  */
117
125
  protected onGamepadConnected(gamepad: Gamepad): void;
118
126
  /**
119
- * Called when the active gamepad disconnects or is replaced through the
120
- * shared input reader.
127
+ * Called when the shared input reader observes a matching disconnection
128
+ * event or a missing/disconnected active slot, including events during pause.
121
129
  *
122
130
  * The default dispatches a `disconnected` event.
123
131
  *
@@ -125,5 +133,4 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
125
133
  */
126
134
  protected onGamepadDisconnected(gamepad: Gamepad): void;
127
135
  }
128
- //#endregion
129
- export { GamepadControls, GamepadControlsEventMap, GamepadControlsOptions };
136
+ //#endregion
@@ -9,12 +9,15 @@ import { EventDispatcher } from "three";
9
9
  */
10
10
  var GamepadControls = class extends EventDispatcher {
11
11
  /**
12
- * When `false`, all input processing is paused.
12
+ * When `false`, `update()` pauses polling and subclass input application,
13
+ * retaining state. Browser connection/disconnection listeners remain active.
14
+ * Assignment alone does not cancel an interaction or dispose the wrapper.
13
15
  * @default true
14
16
  */
15
17
  enabled = true;
16
18
  /**
17
- * The currently active gamepad, or `null` if no gamepad is connected.
19
+ * The active gamepad snapshot, or `null` if none has been adopted.
20
+ * Automatic selection keeps its adopted slot until an observed loss.
18
21
  */
19
22
  gamepad = null;
20
23
  #gamepadInput;
@@ -73,6 +76,8 @@ var GamepadControls = class extends EventDispatcher {
73
76
  }
74
77
  /**
75
78
  * Advances the controller by one frame. Call this inside your render loop.
79
+ * While paused, polling-only losses remain unobserved until updates resume.
80
+ * Resume compares button state with the last observation, without replay.
76
81
  *
77
82
  * @param deltaTime - Seconds since the last frame.
78
83
  */
@@ -80,7 +85,7 @@ var GamepadControls = class extends EventDispatcher {
80
85
  if (!this.enabled) return;
81
86
  this.#gamepadInput.update();
82
87
  this.gamepad = this.#gamepadInput.gamepad;
83
- if (this.gamepad === null) return;
88
+ if (!this.enabled || this.gamepad === null) return;
84
89
  this.onUpdate(deltaTime);
85
90
  }
86
91
  /**
@@ -109,7 +114,9 @@ var GamepadControls = class extends EventDispatcher {
109
114
  return this.#gamepadInput.resetVibration();
110
115
  }
111
116
  /**
112
- * Removes all event listeners attached by this controller. Call when no longer needed.
117
+ * Removes this wrapper's internal input listeners, clears state, and disables
118
+ * it without dispatching a synthetic disconnection. Does not dispose the
119
+ * wrapped Three.js control or affect other wrappers. Reuse is unsupported.
113
120
  */
114
121
  dispose() {
115
122
  this.#gamepadInput.removeEventListener("connected", this.#onGamepadConnected);
@@ -132,8 +139,8 @@ var GamepadControls = class extends EventDispatcher {
132
139
  });
133
140
  }
134
141
  /**
135
- * Called when the active gamepad disconnects or is replaced through the
136
- * shared input reader.
142
+ * Called when the shared input reader observes a matching disconnection
143
+ * event or a missing/disconnected active slot, including events during pause.
137
144
  *
138
145
  * The default dispatches a `disconnected` event.
139
146
  *
@@ -7,7 +7,7 @@ import { DragControls } from "three/addons/controls/DragControls.js";
7
7
  *
8
8
  * Every property has a sensible default, so you only need to pass the properties you want to override.
9
9
  */
10
- type GamepadDragControlsOptions = GamepadControlsOptions & {
10
+ export type GamepadDragControlsOptions = GamepadControlsOptions & {
11
11
  /**
12
12
  * Screen-relative translation speed multiplier.
13
13
  * @default 1.0
@@ -40,7 +40,7 @@ type GamepadDragControlsOptions = GamepadControlsOptions & {
40
40
  * The center of the viewport acts as a logical reticle. Press the select button
41
41
  * once to grab the centered object and again to drop it.
42
42
  */
43
- declare class GamepadDragControls extends GamepadControls {
43
+ export declare class GamepadDragControls extends GamepadControls {
44
44
  #private;
45
45
  /**
46
46
  * @param controls - A Three.js `DragControls` instance.
@@ -56,8 +56,7 @@ declare class GamepadDragControls extends GamepadControls {
56
56
  */
57
57
  protected onUpdate(deltaTime: number): void;
58
58
  /**
59
- * Releases any selected object and removes hover state before disposing the
60
- * gamepad lifecycle listeners.
59
+ * Disables gamepad updates, releases selection, and removes hover state.
61
60
  */
62
61
  dispose(): void;
63
62
  /**
@@ -67,5 +66,4 @@ declare class GamepadDragControls extends GamepadControls {
67
66
  */
68
67
  protected onGamepadDisconnected(gamepad: Gamepad): void;
69
68
  }
70
- //#endregion
71
- export { GamepadDragControls, GamepadDragControlsOptions };
69
+ //#endregion
@@ -73,13 +73,8 @@ var GamepadDragControls = class extends GamepadControls {
73
73
  * @param deltaTime - Seconds since the last frame.
74
74
  */
75
75
  onUpdate(deltaTime) {
76
- const controls = this.#controls;
77
76
  const selectStarted = this.gamepadInput.wasPressed(this.#options.buttonSelect);
78
- if (!controls.enabled) {
79
- this.#releaseSelected();
80
- this.#clearHover();
81
- return;
82
- }
77
+ if (!this.#canApplyInput()) return;
83
78
  const selected = this.#selected;
84
79
  if (selected !== null) {
85
80
  if (selectStarted) {
@@ -91,16 +86,19 @@ var GamepadDragControls = class extends GamepadControls {
91
86
  }
92
87
  const hit = this.#intersectCenter();
93
88
  this.#updateHover(hit?.object ?? null);
94
- if (selectStarted && hit !== void 0) this.#grabObject(hit.object);
89
+ if (!this.#canApplyInput()) return;
90
+ if (selectStarted && hit !== void 0) {
91
+ this.#grabObject(hit.object);
92
+ this.#canApplyInput();
93
+ }
95
94
  }
96
95
  /**
97
- * Releases any selected object and removes hover state before disposing the
98
- * gamepad lifecycle listeners.
96
+ * Disables gamepad updates, releases selection, and removes hover state.
99
97
  */
100
98
  dispose() {
99
+ super.dispose();
101
100
  this.#releaseSelected();
102
101
  this.#clearHover();
103
- super.dispose();
104
102
  }
105
103
  /**
106
104
  * Releases the selected object if the active gamepad disconnects mid-drag.
@@ -112,6 +110,14 @@ var GamepadDragControls = class extends GamepadControls {
112
110
  this.#clearHover();
113
111
  super.onGamepadDisconnected(gamepad);
114
112
  }
113
+ #canApplyInput() {
114
+ if (!this.#controls.enabled) {
115
+ this.#releaseSelected();
116
+ this.#clearHover();
117
+ return false;
118
+ }
119
+ return this.enabled && this.gamepad !== null;
120
+ }
115
121
  /**
116
122
  * Updates the selected object from gamepad drag and rotation input.
117
123
  *
@@ -125,10 +131,13 @@ var GamepadDragControls = class extends GamepadControls {
125
131
  const rotate = input.stick(rotateStick.xAxis, rotateStick.yAxis, rotateStick.pipeline);
126
132
  const dragged = this.#applyDrag(selected, deltaTime, drag.x, drag.y, dragSpeed);
127
133
  const rotated = this.#applyRotation(selected, deltaTime, rotate.x, rotate.y, rotateSpeed);
128
- if (dragged || rotated) this.#controls.dispatchEvent({
129
- type: "drag",
130
- object: selected
131
- });
134
+ if (dragged || rotated) {
135
+ this.#controls.dispatchEvent({
136
+ type: "drag",
137
+ object: selected
138
+ });
139
+ this.#canApplyInput();
140
+ }
132
141
  }
133
142
  /**
134
143
  * Moves the selected object in the camera-facing plane.
@@ -188,7 +197,7 @@ var GamepadDragControls = class extends GamepadControls {
188
197
  #updateHover(object) {
189
198
  if (this.#hovered === object) return;
190
199
  this.#clearHover();
191
- if (object === null) return;
200
+ if (object === null || !this.#canApplyInput()) return;
192
201
  this.#hovered = object;
193
202
  this.#controls.dispatchEvent({
194
203
  type: "hoveron",
@@ -7,7 +7,7 @@ import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.j
7
7
  *
8
8
  * Every property has a sensible default, so you only need to pass the properties you want to override.
9
9
  */
10
- type GamepadFirstPersonControlsOptions = GamepadControlsOptions & {
10
+ export type GamepadFirstPersonControlsOptions = GamepadControlsOptions & {
11
11
  /**
12
12
  * Multiplier on `FirstPersonControls.movementSpeed` for translation.
13
13
  * @default 1.0
@@ -49,7 +49,7 @@ type GamepadFirstPersonControlsOptions = GamepadControlsOptions & {
49
49
  * `gamepadControls.update(delta)` before `controls.update(delta)` each frame.
50
50
  * Bindings and speeds are configurable via {@link GamepadFirstPersonControlsOptions}.
51
51
  */
52
- declare class GamepadFirstPersonControls extends GamepadControls {
52
+ export declare class GamepadFirstPersonControls extends GamepadControls {
53
53
  #private;
54
54
  /**
55
55
  * @param controls - A Three.js `FirstPersonControls` instance.
@@ -64,5 +64,4 @@ declare class GamepadFirstPersonControls extends GamepadControls {
64
64
  */
65
65
  protected onUpdate(deltaTime: number): void;
66
66
  }
67
- //#endregion
68
- export { GamepadFirstPersonControls, GamepadFirstPersonControlsOptions };
67
+ //#endregion
@@ -58,6 +58,7 @@ var GamepadFirstPersonControls = class extends GamepadControls {
58
58
  * @param deltaTime - Seconds since the last frame.
59
59
  */
60
60
  onUpdate(deltaTime) {
61
+ if (!this.#controls.enabled) return;
61
62
  const { moveSpeed, lookSpeed, moveStick, lookStick, buttonDeadzone, buttonMoveUp, buttonMoveDown } = this.#options;
62
63
  this.#applyMovement(deltaTime, moveSpeed, moveStick, buttonDeadzone, buttonMoveUp, buttonMoveDown);
63
64
  this.#applyLook(deltaTime, lookSpeed, lookStick);
@@ -7,7 +7,7 @@ import { FlyControls } from "three/addons/controls/FlyControls.js";
7
7
  *
8
8
  * Every property has a sensible default, so you only need to pass the properties you want to override.
9
9
  */
10
- type GamepadFlyControlsOptions = GamepadControlsOptions & {
10
+ export type GamepadFlyControlsOptions = GamepadControlsOptions & {
11
11
  /**
12
12
  * Multiplier on `FlyControls.movementSpeed` for translation.
13
13
  * @default 1.0
@@ -59,7 +59,7 @@ type GamepadFlyControlsOptions = GamepadControlsOptions & {
59
59
  * `gamepadControls.update(delta)` and `controls.update(delta)` each frame.
60
60
  * Bindings and speeds are configurable via {@link GamepadFlyControlsOptions}.
61
61
  */
62
- declare class GamepadFlyControls extends GamepadControls {
62
+ export declare class GamepadFlyControls extends GamepadControls {
63
63
  #private;
64
64
  /**
65
65
  * @param controls - A Three.js `FlyControls` instance.
@@ -74,5 +74,4 @@ declare class GamepadFlyControls extends GamepadControls {
74
74
  */
75
75
  protected onUpdate(deltaTime: number): void;
76
76
  }
77
- //#endregion
78
- export { GamepadFlyControls, GamepadFlyControlsOptions };
77
+ //#endregion
@@ -55,6 +55,7 @@ var GamepadFlyControls = class extends GamepadControls {
55
55
  * @param deltaTime - Seconds since the last frame.
56
56
  */
57
57
  onUpdate(deltaTime) {
58
+ if (!this.#controls.enabled) return;
58
59
  const { moveSpeed, rotateSpeed, buttonDeadzone, moveStick, lookStick, buttonRollLeft, buttonRollRight, buttonMoveUp, buttonMoveDown } = this.#options;
59
60
  const input = this.gamepadInput;
60
61
  const move = input.stick(moveStick.xAxis, moveStick.yAxis, moveStick.pipeline);
@@ -4,7 +4,7 @@ import { EventDispatcher } from "three";
4
4
  /**
5
5
  * Event map for {@link GamepadInput}.
6
6
  */
7
- type GamepadInputEventMap = {
7
+ export type GamepadInputEventMap = {
8
8
  /**
9
9
  * Fired when a gamepad is connected and becomes active.
10
10
  */
@@ -15,7 +15,9 @@ type GamepadInputEventMap = {
15
15
  gamepad: Gamepad;
16
16
  };
17
17
  /**
18
- * Fired when the active gamepad is disconnected or replaced in its slot.
18
+ * Fired on a matching browser disconnection event or when polling observes
19
+ * the active slot missing or disconnected. Continuous snapshots in the same
20
+ * slot do not establish a physical-device identity or signal replacement.
19
21
  */
20
22
  disconnected: {
21
23
  /**
@@ -27,7 +29,7 @@ type GamepadInputEventMap = {
27
29
  /**
28
30
  * Configuration for {@link GamepadInput}.
29
31
  */
30
- type GamepadInputOptions = {
32
+ export type GamepadInputOptions = {
31
33
  /**
32
34
  * Default dead zone threshold for {@link GamepadInput.axis} reads.
33
35
  * @default 0.1
@@ -41,7 +43,9 @@ type GamepadInputOptions = {
41
43
  /**
42
44
  * Browser-assigned gamepad slot to use.
43
45
  *
44
- * When omitted, the connected gamepad with the lowest index is selected.
46
+ * When omitted, the lowest connected index is chosen at adoption and kept
47
+ * until its loss is observed, even if a lower index subsequently connects.
48
+ * Multiple unconfigured instances may adopt the same slot.
45
49
  * The index must be an integer from `MIN_GAMEPAD_INDEX` through
46
50
  * `MAX_GAMEPAD_INDEX`.
47
51
  * A valid but empty slot keeps this input disconnected without falling back
@@ -52,7 +56,7 @@ type GamepadInputOptions = {
52
56
  /**
53
57
  * Options for axis reads.
54
58
  */
55
- type GamepadAxisOptions = {
59
+ export type GamepadAxisOptions = {
56
60
  /**
57
61
  * Axis dead zone threshold for this read.
58
62
  */
@@ -63,10 +67,12 @@ type GamepadAxisOptions = {
63
67
  *
64
68
  * Call {@link update} once per frame before reading button transitions or axes.
65
69
  */
66
- declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
70
+ export declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
67
71
  #private;
68
72
  /**
69
- * When `false`, input polling is paused.
73
+ * When `false`, polling through `update()` is paused and the last observed
74
+ * state, including button transitions, is retained. Browser listeners remain
75
+ * active and may adopt or disconnect a gamepad; connection events may poll.
70
76
  * @default true
71
77
  */
72
78
  enabled: boolean;
@@ -112,10 +118,15 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
112
118
  get vibrationSupported(): boolean;
113
119
  /**
114
120
  * Polls the gamepad and refreshes current and previous button state.
121
+ *
122
+ * Resuming compares against the last observed state, without replaying clicks
123
+ * completed during the pause. Adoption seeds held buttons without transitions.
115
124
  */
116
125
  update(): void;
117
126
  /**
118
- * Removes all window-level event listeners attached by this input reader.
127
+ * Removes this reader's window listeners, clears its state, and disables it.
128
+ * Does not dispatch a synthetic disconnection or affect other input readers.
129
+ * Reusing a disposed reader is unsupported; create a new instance instead.
119
130
  */
120
131
  dispose(): void;
121
132
  /**
@@ -126,17 +137,19 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
126
137
  */
127
138
  isPressed(button: number): boolean;
128
139
  /**
129
- * Returns whether a button was pressed during the latest update.
140
+ * Returns whether a button changed to pressed in the latest observed state.
141
+ * Paused updates retain this result until the next observation or cleanup.
130
142
  *
131
143
  * @param button - Button index to inspect.
132
- * @returns `true` only on the frame where the button transitions to pressed.
144
+ * @returns Whether the latest observed button state transitioned to pressed.
133
145
  */
134
146
  wasPressed(button: number): boolean;
135
147
  /**
136
- * Returns whether a button was released during the latest update.
148
+ * Returns whether a button changed to released in the latest observed state.
149
+ * Paused updates retain this result until the next observation or cleanup.
137
150
  *
138
151
  * @param button - Button index to inspect.
139
- * @returns `true` only on the frame where the button transitions to released.
152
+ * @returns Whether the latest observed button state transitioned to released.
140
153
  */
141
154
  wasReleased(button: number): boolean;
142
155
  /**
@@ -185,5 +198,4 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
185
198
  */
186
199
  resetVibration(): Promise<GamepadHapticsResult | null>;
187
200
  }
188
- //#endregion
189
- export { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions };
201
+ //#endregion