@flare-engine/input 0.3.1 → 0.4.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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @flare-engine/input
2
2
 
3
- Pointer / touch / gesture / virtual-control / gamepad / keyboard input. `InputManager` polls per frame; `Pointer` exposes primary touch with `isDown` / `justPressed` / `delta`; `GestureDetector` emits tap / swipe / pinch / long-press; data-only `VirtualJoystick` + `VirtualButton` state (RN components live in `@flare-engine/react`); `GamepadSource` + `RemapTable` cover gamepad polling and persistent rebinds; `createKeyboard` provides buffered `isDown` / `wasPressed` / `wasReleased` edges per `KeyboardEvent.code` with blur-clears-held semantics.
3
+ Pointer / touch / gesture / virtual-control / gamepad / keyboard input. `InputManager` polls per frame; `Pointer` exposes the primary touch (the finger that started the current gesture) with `isDown` / `justPressed` / `delta` — other simultaneous touches reach only `touches[]`, never `pointer`, and when the primary lifts it does not transfer to a remaining finger: `pointer` stays unset until every touch is up and a fresh press starts a new primary; `GestureDetector` emits tap / swipe / pinch / long-press; data-only `VirtualJoystick` + `VirtualButton` state (RN components live in `@flare-engine/react`); `GamepadSource` + `RemapTable` cover gamepad polling and persistent rebinds; `createKeyboard` provides buffered `isDown` / `wasPressed` / `wasReleased` edges per `KeyboardEvent.code` with blur-clears-held semantics.
4
+
5
+ Per-frame edge flags come in two deliberate shapes: per-thing boolean fields (`Pointer.justPressed/justReleased`, `VirtualButtonState.justPressed/justReleased`, `GamepadButtonState.justPressed/justReleased`) for state you already hold a reference to, and keyed query functions (`KeyboardState.wasPressed/wasReleased(code)`, `wasButtonPressed/wasButtonReleased(state, button)`) for state addressed by an id.
4
6
 
5
7
  ## Install
6
8
 
package/dist/index.d.mts CHANGED
@@ -41,9 +41,9 @@ interface GamepadButtonState {
41
41
  pressed: boolean;
42
42
  value: number;
43
43
  /** True for exactly one frame after a press. */
44
- pressedThisFrame: boolean;
44
+ justPressed: boolean;
45
45
  /** True for exactly one frame after a release. */
46
- releasedThisFrame: boolean;
46
+ justReleased: boolean;
47
47
  }
48
48
  interface GamepadState {
49
49
  connected: boolean;
@@ -68,7 +68,7 @@ interface GamepadSnapshot {
68
68
  declare function createGamepadState(index?: number, buttonCount?: number, axisCount?: number): GamepadState;
69
69
  /**
70
70
  * Apply a fresh snapshot to an existing `GamepadState` in place. Derived
71
- * `pressedThisFrame` / `releasedThisFrame` flags are recomputed from the
71
+ * `justPressed` / `justReleased` flags are recomputed from the
72
72
  * previous `pressed` value.
73
73
  *
74
74
  * `deadZone` in [0, 1). Values at or below are treated as 0.
@@ -91,6 +91,11 @@ declare function getAxis(state: GamepadState, axis: number): number;
91
91
  * - `createWebGamepadSource()` wraps the W3C `navigator.getGamepads` API.
92
92
  * - Native implementations (e.g., `react-native-gamepad`) inject their own
93
93
  * via the `GamepadSource` interface — the package itself ships no native deps.
94
+ *
95
+ * **Freshness contract:** each call returns fresh objects. An implementation
96
+ * must not reuse or mutate a snapshot (or its `buttons`/`axes` arrays) that a
97
+ * previous `poll()` call returned. A caller may therefore retain a returned
98
+ * snapshot across frames without it changing out from under them.
94
99
  */
95
100
  interface GamepadSource {
96
101
  poll(): (GamepadSnapshot | null)[];
@@ -159,7 +164,8 @@ declare class GestureDetector {
159
164
  private swipeMinDistance;
160
165
  private swipeMaxDuration;
161
166
  private _startPos;
162
- private _startTime;
167
+ /** Elapsed time (seconds) since `pointerDown` for the in-progress press. */
168
+ private _elapsed;
163
169
  private _isTracking;
164
170
  private _longPressFired;
165
171
  private _pinchStartDistance;
@@ -167,8 +173,18 @@ declare class GestureDetector {
167
173
  constructor(config?: GestureConfig);
168
174
  /** Feed a pointer-down event. */
169
175
  pointerDown(x: number, y: number): void;
170
- /** Feed a pointer-up event. */
171
- pointerUp(x: number, y: number, elapsedSinceDown: number): void;
176
+ /**
177
+ * Feed a pointer-up event.
178
+ *
179
+ * `elapsedSinceDown` is optional: pass it when the host tracks its own
180
+ * timestamps (e.g. event-driven, no per-frame `update()` calls); omit it
181
+ * to use the elapsed time already accumulated via `update(dt, ...)`. Either
182
+ * way there is one clock per press — passing it here re-syncs the same
183
+ * accumulator `update()` advances, rather than racing it. If a long press
184
+ * already fired for this press, tap/swipe are suppressed so one press
185
+ * yields exactly one gesture.
186
+ */
187
+ pointerUp(x: number, y: number, elapsedSinceDown?: number): void;
172
188
  /**
173
189
  * Update gesture tracking. Call each frame with dt.
174
190
  * @param dt Delta time in seconds.
@@ -225,8 +241,6 @@ interface KeyboardState {
225
241
  }
226
242
  /** Create a fresh keyboard state. */
227
243
  declare function createKeyboard(): KeyboardState;
228
- /** Free-function parity with `resetPointerFrame`. Delegates to `kb.reset()`. */
229
- declare function resetKeyboardFrame(kb: KeyboardState): void;
230
244
  /**
231
245
  * Web turnkey adapter: registers `keydown`, `keyup`, and `blur` listeners on
232
246
  * `target`. `blur` calls `kb.clear()` (stuck-key guard). Returns a detach fn.
@@ -336,13 +350,26 @@ declare function loadRemap(store: RemapStore, key: string): Promise<RemapTable |
336
350
  * Feed events from the platform (RN touch/gesture handlers) via the on* methods.
337
351
  */
338
352
  declare class InputManager {
339
- /** Primary pointer (first touch or mouse). */
353
+ /**
354
+ * Primary pointer — the touch that started the current gesture (first
355
+ * `onPointerDown` while no touch was active), or the mouse.
356
+ *
357
+ * Once assigned, the primary id stays fixed to that touch until it lifts;
358
+ * it does NOT transfer to a remaining finger. While a second finger is
359
+ * down, its events reach only `touches` — `pointer` neither moves nor
360
+ * releases because of it. When the primary lifts, `pointer.isDown` goes
361
+ * false even if other fingers remain down, and a new primary is assigned
362
+ * only once every touch has lifted and a fresh `onPointerDown` arrives.
363
+ * This keeps `pointer` a single continuous stream with no mid-gesture
364
+ * teleport; read `touches` for anything beyond one finger.
365
+ */
340
366
  readonly pointer: Pointer;
341
367
  /** Buffered keyboard state. Feed via `attachKeyboardListeners` (web) or `keyboard.keyDown`/`keyUp` (mobile/RN). */
342
368
  readonly keyboard: KeyboardState;
343
369
  /** All active touch points. */
344
370
  readonly touches: TouchPoint[];
345
371
  private _touchMap;
372
+ private _primaryTouchId;
346
373
  private _gamepads;
347
374
  private _gamepadDeadZone;
348
375
  private _gamepadRemaps;
@@ -420,5 +447,5 @@ declare function updateJoystick(state: VirtualJoystickState, centerX: number, ce
420
447
  /** Reset joystick state when touch is released. */
421
448
  declare function releaseJoystick(state: VirtualJoystickState): void;
422
449
  //#endregion
423
- export { type AxisMap, GamepadAxis, type GamepadAxisId, GamepadButton, type GamepadButtonId, type GamepadButtonState, type GamepadSnapshot, type GamepadSource, type GamepadState, type GestureConfig, GestureDetector, InputManager, type KeyBindings, type KeyboardEventLike, type KeyboardEventTargetLike, type KeyboardState, type PinchEvent, type Pointer, type RemapStore, type RemapTable, type SwipeDirection, type SwipeEvent, type TouchPoint, type VirtualButtonState, type VirtualJoystickConfig, type VirtualJoystickState, applyRemap, attachKeyboardListeners, createButtonState, createGamepadState, createIdentityRemap, createJoystickState, createKeyboard, createPointer, createWebGamepadSource, getAxis, isActionDown, isButtonDown, loadRemap, pointerDown, pointerMove, pointerUp, pressButton, releaseButton, releaseJoystick, resetButtonFrame, resetGamepadFrame, resetKeyboardFrame, resetPointerFrame, saveRemap, updateGamepadFromSnapshot, updateJoystick, wasActionPressed, wasActionReleased, wasButtonPressed, wasButtonReleased };
450
+ export { type AxisMap, GamepadAxis, type GamepadAxisId, GamepadButton, type GamepadButtonId, type GamepadButtonState, type GamepadSnapshot, type GamepadSource, type GamepadState, type GestureConfig, GestureDetector, InputManager, type KeyBindings, type KeyboardEventLike, type KeyboardEventTargetLike, type KeyboardState, type PinchEvent, type Pointer, type RemapStore, type RemapTable, type SwipeDirection, type SwipeEvent, type TouchPoint, type VirtualButtonState, type VirtualJoystickConfig, type VirtualJoystickState, applyRemap, attachKeyboardListeners, createButtonState, createGamepadState, createIdentityRemap, createJoystickState, createKeyboard, createPointer, createWebGamepadSource, getAxis, isActionDown, isButtonDown, loadRemap, pointerDown, pointerMove, pointerUp, pressButton, releaseButton, releaseJoystick, resetButtonFrame, resetGamepadFrame, resetPointerFrame, saveRemap, updateGamepadFromSnapshot, updateJoystick, wasActionPressed, wasActionReleased, wasButtonPressed, wasButtonReleased };
424
451
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/gamepad.ts","../src/gamepad-source.ts","../src/gestures.ts","../src/keyboard.ts","../src/pointer.ts","../src/remap.ts","../src/input-manager.ts","../src/virtual-button.ts","../src/virtual-joystick.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/gamepad.ts","../src/gamepad-source.ts","../src/gestures.ts","../src/keyboard.ts","../src/pointer.ts","../src/remap.ts","../src/input-manager.ts","../src/virtual-button.ts","../src/virtual-joystick.ts"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -41,9 +41,9 @@ interface GamepadButtonState {
41
41
  pressed: boolean;
42
42
  value: number;
43
43
  /** True for exactly one frame after a press. */
44
- pressedThisFrame: boolean;
44
+ justPressed: boolean;
45
45
  /** True for exactly one frame after a release. */
46
- releasedThisFrame: boolean;
46
+ justReleased: boolean;
47
47
  }
48
48
  interface GamepadState {
49
49
  connected: boolean;
@@ -68,7 +68,7 @@ interface GamepadSnapshot {
68
68
  declare function createGamepadState(index?: number, buttonCount?: number, axisCount?: number): GamepadState;
69
69
  /**
70
70
  * Apply a fresh snapshot to an existing `GamepadState` in place. Derived
71
- * `pressedThisFrame` / `releasedThisFrame` flags are recomputed from the
71
+ * `justPressed` / `justReleased` flags are recomputed from the
72
72
  * previous `pressed` value.
73
73
  *
74
74
  * `deadZone` in [0, 1). Values at or below are treated as 0.
@@ -91,6 +91,11 @@ declare function getAxis(state: GamepadState, axis: number): number;
91
91
  * - `createWebGamepadSource()` wraps the W3C `navigator.getGamepads` API.
92
92
  * - Native implementations (e.g., `react-native-gamepad`) inject their own
93
93
  * via the `GamepadSource` interface — the package itself ships no native deps.
94
+ *
95
+ * **Freshness contract:** each call returns fresh objects. An implementation
96
+ * must not reuse or mutate a snapshot (or its `buttons`/`axes` arrays) that a
97
+ * previous `poll()` call returned. A caller may therefore retain a returned
98
+ * snapshot across frames without it changing out from under them.
94
99
  */
95
100
  interface GamepadSource {
96
101
  poll(): (GamepadSnapshot | null)[];
@@ -159,7 +164,8 @@ declare class GestureDetector {
159
164
  private swipeMinDistance;
160
165
  private swipeMaxDuration;
161
166
  private _startPos;
162
- private _startTime;
167
+ /** Elapsed time (seconds) since `pointerDown` for the in-progress press. */
168
+ private _elapsed;
163
169
  private _isTracking;
164
170
  private _longPressFired;
165
171
  private _pinchStartDistance;
@@ -167,8 +173,18 @@ declare class GestureDetector {
167
173
  constructor(config?: GestureConfig);
168
174
  /** Feed a pointer-down event. */
169
175
  pointerDown(x: number, y: number): void;
170
- /** Feed a pointer-up event. */
171
- pointerUp(x: number, y: number, elapsedSinceDown: number): void;
176
+ /**
177
+ * Feed a pointer-up event.
178
+ *
179
+ * `elapsedSinceDown` is optional: pass it when the host tracks its own
180
+ * timestamps (e.g. event-driven, no per-frame `update()` calls); omit it
181
+ * to use the elapsed time already accumulated via `update(dt, ...)`. Either
182
+ * way there is one clock per press — passing it here re-syncs the same
183
+ * accumulator `update()` advances, rather than racing it. If a long press
184
+ * already fired for this press, tap/swipe are suppressed so one press
185
+ * yields exactly one gesture.
186
+ */
187
+ pointerUp(x: number, y: number, elapsedSinceDown?: number): void;
172
188
  /**
173
189
  * Update gesture tracking. Call each frame with dt.
174
190
  * @param dt Delta time in seconds.
@@ -225,8 +241,6 @@ interface KeyboardState {
225
241
  }
226
242
  /** Create a fresh keyboard state. */
227
243
  declare function createKeyboard(): KeyboardState;
228
- /** Free-function parity with `resetPointerFrame`. Delegates to `kb.reset()`. */
229
- declare function resetKeyboardFrame(kb: KeyboardState): void;
230
244
  /**
231
245
  * Web turnkey adapter: registers `keydown`, `keyup`, and `blur` listeners on
232
246
  * `target`. `blur` calls `kb.clear()` (stuck-key guard). Returns a detach fn.
@@ -336,13 +350,26 @@ declare function loadRemap(store: RemapStore, key: string): Promise<RemapTable |
336
350
  * Feed events from the platform (RN touch/gesture handlers) via the on* methods.
337
351
  */
338
352
  declare class InputManager {
339
- /** Primary pointer (first touch or mouse). */
353
+ /**
354
+ * Primary pointer — the touch that started the current gesture (first
355
+ * `onPointerDown` while no touch was active), or the mouse.
356
+ *
357
+ * Once assigned, the primary id stays fixed to that touch until it lifts;
358
+ * it does NOT transfer to a remaining finger. While a second finger is
359
+ * down, its events reach only `touches` — `pointer` neither moves nor
360
+ * releases because of it. When the primary lifts, `pointer.isDown` goes
361
+ * false even if other fingers remain down, and a new primary is assigned
362
+ * only once every touch has lifted and a fresh `onPointerDown` arrives.
363
+ * This keeps `pointer` a single continuous stream with no mid-gesture
364
+ * teleport; read `touches` for anything beyond one finger.
365
+ */
340
366
  readonly pointer: Pointer;
341
367
  /** Buffered keyboard state. Feed via `attachKeyboardListeners` (web) or `keyboard.keyDown`/`keyUp` (mobile/RN). */
342
368
  readonly keyboard: KeyboardState;
343
369
  /** All active touch points. */
344
370
  readonly touches: TouchPoint[];
345
371
  private _touchMap;
372
+ private _primaryTouchId;
346
373
  private _gamepads;
347
374
  private _gamepadDeadZone;
348
375
  private _gamepadRemaps;
@@ -420,5 +447,5 @@ declare function updateJoystick(state: VirtualJoystickState, centerX: number, ce
420
447
  /** Reset joystick state when touch is released. */
421
448
  declare function releaseJoystick(state: VirtualJoystickState): void;
422
449
  //#endregion
423
- export { type AxisMap, GamepadAxis, type GamepadAxisId, GamepadButton, type GamepadButtonId, type GamepadButtonState, type GamepadSnapshot, type GamepadSource, type GamepadState, type GestureConfig, GestureDetector, InputManager, type KeyBindings, type KeyboardEventLike, type KeyboardEventTargetLike, type KeyboardState, type PinchEvent, type Pointer, type RemapStore, type RemapTable, type SwipeDirection, type SwipeEvent, type TouchPoint, type VirtualButtonState, type VirtualJoystickConfig, type VirtualJoystickState, applyRemap, attachKeyboardListeners, createButtonState, createGamepadState, createIdentityRemap, createJoystickState, createKeyboard, createPointer, createWebGamepadSource, getAxis, isActionDown, isButtonDown, loadRemap, pointerDown, pointerMove, pointerUp, pressButton, releaseButton, releaseJoystick, resetButtonFrame, resetGamepadFrame, resetKeyboardFrame, resetPointerFrame, saveRemap, updateGamepadFromSnapshot, updateJoystick, wasActionPressed, wasActionReleased, wasButtonPressed, wasButtonReleased };
450
+ export { type AxisMap, GamepadAxis, type GamepadAxisId, GamepadButton, type GamepadButtonId, type GamepadButtonState, type GamepadSnapshot, type GamepadSource, type GamepadState, type GestureConfig, GestureDetector, InputManager, type KeyBindings, type KeyboardEventLike, type KeyboardEventTargetLike, type KeyboardState, type PinchEvent, type Pointer, type RemapStore, type RemapTable, type SwipeDirection, type SwipeEvent, type TouchPoint, type VirtualButtonState, type VirtualJoystickConfig, type VirtualJoystickState, applyRemap, attachKeyboardListeners, createButtonState, createGamepadState, createIdentityRemap, createJoystickState, createKeyboard, createPointer, createWebGamepadSource, getAxis, isActionDown, isButtonDown, loadRemap, pointerDown, pointerMove, pointerUp, pressButton, releaseButton, releaseJoystick, resetButtonFrame, resetGamepadFrame, resetPointerFrame, saveRemap, updateGamepadFromSnapshot, updateJoystick, wasActionPressed, wasActionReleased, wasButtonPressed, wasButtonReleased };
424
451
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/gamepad.ts","../src/gamepad-source.ts","../src/gestures.ts","../src/keyboard.ts","../src/pointer.ts","../src/remap.ts","../src/input-manager.ts","../src/virtual-button.ts","../src/virtual-joystick.ts"],"mappings":";;;;;;;;;;;cASa;;;;;;;;;;;;;;;;;;;KAmBD,0BAA0B,4BAA4B;;cAGrD;;;;;;KAMD,wBAAwB,0BAA0B;UAE7C;EACf;EACA;;EAEA;;EAEA;;UAGe;EACf;EACA;EACA;EACA,SAAS;EACT;;EAEA;;;UAIe;EACf;EACA;EACA;IAAW;IAAkB;;EAC7B;EACA;;iBAGc,mBAAmB,gBAAW,sBAAkB,qBAAgB;;;;;;;;iBAwBhE,0BACd,OAAO,cACP,UAAU,wBACV;;iBAyDc,kBAAkB,OAAO;iBAoBzB,aAAa,OAAO,cAAc;iBAIlC,iBAAiB,OAAO,cAAc;iBAItC,kBAAkB,OAAO,cAAc;iBAIvC,QAAQ,OAAO,cAAc;;;;;;;;;;;;;UC3K5B;EACf,SAAS;;UAGD;EACR,qBAAqB;;UAGb;EACR;EACA;EACA;EACA;EACA;IAAoB;IAAkB;;EACtC;;;;;;iBAOc,uBAAuB,MAAM,gBAAgB;;;;KC7BjD;;UAGK;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe;EACf,WAAW;EACX;EACA;;;UAIe;;EAEf;;EAEA,QAAQ;;;;;;cAaG;WACF,OAAK,OAAA;WACL,aAAW,OAAA;WACX,SAAO,OAAA;WACP,SAAO,OAAA;UAER;UACA;UACA;UACA;UACA;UAGA;UACA;UACA;UACA;UAGA;UACA;cAEI,SAAS;;EASrB,YAAY,WAAW;;EAQvB,UAAU,WAAW,WAAW;;;;;;;EAoChC,OAAO,YAAY,kBAAkB;;;;;;EAqBrC,YAAY,IAAI,MAAM,IAAI;;EAuB1B;;;;;UC3Je;WACN;WACA;;EAET;;;UAIe;EACf,iBAAiB,cAAc,UAAU,IAAI;EAC7C,oBAAoB,cAAc,UAAU,IAAI;;;;;;;;;;UAWjC;;EAEf,OAAO;;EAEP,WAAW;;EAEX,YAAY;;EAEZ,QAAQ;;EAER,MAAM;;EAEN;;EAEA;;;iBAIc,kBAAkB;;iBAsDlB,mBAAmB,IAAI;;;;;;;;;;;;;iBAgBvB,wBACd,IAAI,eACJ,QAAQ;;;;;;KA4BE,cAAc,SAAS;;iBAGnB,aAAa,IAAI,eAAe,UAAU,aAAa;;iBAUvD,iBACd,IAAI,eACJ,UAAU,aACV;;;;;iBAcc,kBACd,IAAI,eACJ,UAAU,aACV;;;;UClLe;;EAEf,UAAU;;EAEV,OAAO;;EAEP;;EAEA;;EAEA;;;UAIe;;EAEf;;EAEA,UAAU;;EAEV;;;iBAIc,iBAAiB;;;;;iBAcjB,kBAAkB,SAAS;;iBAQ3B,YAAY,SAAS,SAAS,WAAW;;iBAazC,YAAY,SAAS,SAAS,WAAW;;iBAQzC,UAAU,SAAS,SAAS,WAAW;;;;;;;;;;;;UC3DtC;EACf;EACA;EACA;;UAGe;EACf;EACA,MAAM;;iBAGQ,oBAAoB,sBAAkB,qBAAgB;;iBAStD,WAAW,UAAU,iBAAiB,OAAO,aAAa;;;;;UAoBzD;EACf,IAAI,cAAc;EAClB,IAAI,aAAa,gBAAgB;;;iBAIb,UAAU,OAAO,YAAY,aAAa,OAAO,aAAa;;iBAK9D,UAAU,OAAO,YAAY,cAAc,QAAQ;;;;;;;;;cCpC5D;;WAEF,SAAS;;WAGT,UAAU;;WAGV,SAAS;UAEV;UACA;UACA;UACA;;EAGR;;EAUA,cAAc,YAAY,WAAW;;EAMrC,cAAc,YAAY,WAAW;;EAMrC,YAAY,YAAY,WAAW;;EAMnC,QAAQ,gBAAgB;;EAUxB,mBAAmB;;EAKnB,gBAAgB,eAAe,OAAO;;EAMtC,aAAa,QAAQ;UAeb;UAaA;;;;;UCtHO;;EAEf;;EAEA;;EAEA;;;iBAIc,qBAAqB;;iBASrB,iBAAiB,OAAO;;iBAMxB,YAAY,OAAO;;iBAQnB,cAAc,OAAO;;;;UC/BpB;;EAEf;;EAEA;;;UAIe;;EAEf;;EAEA,WAAW;;EAEX;;EAEA,QAAQ;;;iBAOM,uBAAuB;;;;;;;;;;;iBAmBvB,eACd,OAAO,sBACP,iBACA,iBACA,gBACA,gBACA,SAAS;;iBAqCK,gBAAgB,OAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/gamepad.ts","../src/gamepad-source.ts","../src/gestures.ts","../src/keyboard.ts","../src/pointer.ts","../src/remap.ts","../src/input-manager.ts","../src/virtual-button.ts","../src/virtual-joystick.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -8,6 +8,16 @@ let _flare_engine_events = require("@flare-engine/events");
8
8
  * default. Callers can translate raw indices via `remap.ts` before feeding
9
9
  * snapshots into `updateGamepadFromSnapshot`.
10
10
  */
11
+ /**
12
+ * Dev-flag access for the dead-zone guard, which runs per axis per frame. Read
13
+ * as a `globalThis` property rather than a bare `__DEV__` identifier for the
14
+ * reason measured in PERF-10 (~0.46 ns vs ~270 ns on JSC).
15
+ */
16
+ const G$1 = globalThis;
17
+ /** Warn at most once per process; a bad dead zone repeats every frame. */
18
+ let _warnedDeadZone = false;
19
+ /** Largest dead zone that still leaves the rescale well-defined. */
20
+ const MAX_DEAD_ZONE = .99;
11
21
  /** W3C Standard Gamepad button indices. */
12
22
  const GamepadButton = {
13
23
  A: 0,
@@ -40,8 +50,8 @@ function createGamepadState(index = 0, buttonCount = 17, axisCount = 4) {
40
50
  for (let i = 0; i < buttonCount; i++) buttons[i] = {
41
51
  pressed: false,
42
52
  value: 0,
43
- pressedThisFrame: false,
44
- releasedThisFrame: false
53
+ justPressed: false,
54
+ justReleased: false
45
55
  };
46
56
  return {
47
57
  connected: false,
@@ -53,13 +63,21 @@ function createGamepadState(index = 0, buttonCount = 17, axisCount = 4) {
53
63
  };
54
64
  }
55
65
  function applyDeadZone(value, deadZone) {
66
+ let dz = deadZone;
67
+ if (!(dz >= 0 && dz < 1)) {
68
+ if (G$1.__DEV__ && !_warnedDeadZone) {
69
+ _warnedDeadZone = true;
70
+ console.warn(`[flare-engine/input] deadZone=${deadZone} is outside [0, 1); clamping. Values at or above 1 would suppress the axis entirely.`);
71
+ }
72
+ dz = dz >= 1 ? MAX_DEAD_ZONE : 0;
73
+ }
56
74
  const abs = Math.abs(value);
57
- if (abs <= deadZone) return 0;
58
- return (value < 0 ? -1 : 1) * ((abs - deadZone) / (1 - deadZone));
75
+ if (!(abs > dz)) return 0;
76
+ return (value < 0 ? -1 : 1) * ((abs - dz) / (1 - dz));
59
77
  }
60
78
  /**
61
79
  * Apply a fresh snapshot to an existing `GamepadState` in place. Derived
62
- * `pressedThisFrame` / `releasedThisFrame` flags are recomputed from the
80
+ * `justPressed` / `justReleased` flags are recomputed from the
63
81
  * previous `pressed` value.
64
82
  *
65
83
  * `deadZone` in [0, 1). Values at or below are treated as 0.
@@ -73,8 +91,8 @@ function updateGamepadFromSnapshot(state, snapshot, deadZone = 0) {
73
91
  for (let i = 0; i < state.buttons.length; i++) {
74
92
  const b = state.buttons[i];
75
93
  if (!b) continue;
76
- b.pressedThisFrame = false;
77
- b.releasedThisFrame = b.pressed;
94
+ b.justPressed = false;
95
+ b.justReleased = b.pressed;
78
96
  b.pressed = false;
79
97
  b.value = 0;
80
98
  }
@@ -93,17 +111,17 @@ function updateGamepadFromSnapshot(state, snapshot, deadZone = 0) {
93
111
  const prev = state.buttons[i];
94
112
  const src = srcButtons[i];
95
113
  if (!prev || !src) continue;
96
- const wasPressed = prev.pressed;
114
+ const wasDown = prev.pressed;
97
115
  prev.pressed = src.pressed;
98
116
  prev.value = src.value;
99
- prev.pressedThisFrame = src.pressed && !wasPressed;
100
- prev.releasedThisFrame = !src.pressed && wasPressed;
117
+ prev.justPressed = src.pressed && !wasDown;
118
+ prev.justReleased = !src.pressed && wasDown;
101
119
  }
102
120
  for (let i = bLen; i < state.buttons.length; i++) {
103
121
  const b = state.buttons[i];
104
122
  if (!b) continue;
105
- b.pressedThisFrame = false;
106
- b.releasedThisFrame = b.pressed;
123
+ b.justPressed = false;
124
+ b.justReleased = b.pressed;
107
125
  b.pressed = false;
108
126
  b.value = 0;
109
127
  }
@@ -119,8 +137,8 @@ function clearFrameFlags(state) {
119
137
  for (let i = 0; i < state.buttons.length; i++) {
120
138
  const b = state.buttons[i];
121
139
  if (!b) continue;
122
- b.pressedThisFrame = false;
123
- b.releasedThisFrame = false;
140
+ b.justPressed = false;
141
+ b.justReleased = false;
124
142
  }
125
143
  }
126
144
  function anyButtonDown(state) {
@@ -131,10 +149,10 @@ function isButtonDown(state, button) {
131
149
  return state.buttons[button]?.pressed ?? false;
132
150
  }
133
151
  function wasButtonPressed(state, button) {
134
- return state.buttons[button]?.pressedThisFrame ?? false;
152
+ return state.buttons[button]?.justPressed ?? false;
135
153
  }
136
154
  function wasButtonReleased(state, button) {
137
- return state.buttons[button]?.releasedThisFrame ?? false;
155
+ return state.buttons[button]?.justReleased ?? false;
138
156
  }
139
157
  function getAxis(state, axis) {
140
158
  return state.axes[axis] ?? 0;
@@ -206,7 +224,8 @@ var GestureDetector = class {
206
224
  x: 0,
207
225
  y: 0
208
226
  };
209
- _startTime = 0;
227
+ /** Elapsed time (seconds) since `pointerDown` for the in-progress press. */
228
+ _elapsed = 0;
210
229
  _isTracking = false;
211
230
  _longPressFired = false;
212
231
  _pinchStartDistance = 0;
@@ -224,18 +243,30 @@ var GestureDetector = class {
224
243
  x,
225
244
  y
226
245
  };
227
- this._startTime = 0;
246
+ this._elapsed = 0;
228
247
  this._isTracking = true;
229
248
  this._longPressFired = false;
230
249
  }
231
- /** Feed a pointer-up event. */
250
+ /**
251
+ * Feed a pointer-up event.
252
+ *
253
+ * `elapsedSinceDown` is optional: pass it when the host tracks its own
254
+ * timestamps (e.g. event-driven, no per-frame `update()` calls); omit it
255
+ * to use the elapsed time already accumulated via `update(dt, ...)`. Either
256
+ * way there is one clock per press — passing it here re-syncs the same
257
+ * accumulator `update()` advances, rather than racing it. If a long press
258
+ * already fired for this press, tap/swipe are suppressed so one press
259
+ * yields exactly one gesture.
260
+ */
232
261
  pointerUp(x, y, elapsedSinceDown) {
233
262
  if (!this._isTracking) return;
234
263
  this._isTracking = false;
264
+ if (elapsedSinceDown !== void 0) this._elapsed = elapsedSinceDown;
265
+ if (this._longPressFired) return;
235
266
  const dx = x - this._startPos.x;
236
267
  const dy = y - this._startPos.y;
237
268
  const dist = Math.sqrt(dx * dx + dy * dy);
238
- const duration = elapsedSinceDown;
269
+ const duration = this._elapsed;
239
270
  if (dist >= this.swipeMinDistance && duration <= this.swipeMaxDuration) {
240
271
  const absDx = Math.abs(dx);
241
272
  const absDy = Math.abs(dy);
@@ -265,8 +296,8 @@ var GestureDetector = class {
265
296
  */
266
297
  update(dt, currentX, currentY) {
267
298
  if (!this._isTracking) return;
268
- this._startTime += dt;
269
- if (!this._longPressFired && this._startTime >= this.longPressMinDuration) {
299
+ this._elapsed += dt;
300
+ if (!this._longPressFired && this._elapsed >= this.longPressMinDuration) {
270
301
  const dx = currentX - this._startPos.x;
271
302
  const dy = currentY - this._startPos.y;
272
303
  if (Math.sqrt(dx * dx + dy * dy) <= this.tapMaxDistance) {
@@ -383,10 +414,6 @@ function createKeyboard() {
383
414
  }
384
415
  };
385
416
  }
386
- /** Free-function parity with `resetPointerFrame`. Delegates to `kb.reset()`. */
387
- function resetKeyboardFrame(kb) {
388
- kb.reset();
389
- }
390
417
  /**
391
418
  * Web turnkey adapter: registers `keydown`, `keyup`, and `blur` listeners on
392
419
  * `target`. `blur` calls `kb.clear()` (stuck-key guard). Returns a detach fn.
@@ -581,20 +608,33 @@ function isRemapTable(value) {
581
608
  * Feed events from the platform (RN touch/gesture handlers) via the on* methods.
582
609
  */
583
610
  var InputManager = class {
584
- /** Primary pointer (first touch or mouse). */
611
+ /**
612
+ * Primary pointer — the touch that started the current gesture (first
613
+ * `onPointerDown` while no touch was active), or the mouse.
614
+ *
615
+ * Once assigned, the primary id stays fixed to that touch until it lifts;
616
+ * it does NOT transfer to a remaining finger. While a second finger is
617
+ * down, its events reach only `touches` — `pointer` neither moves nor
618
+ * releases because of it. When the primary lifts, `pointer.isDown` goes
619
+ * false even if other fingers remain down, and a new primary is assigned
620
+ * only once every touch has lifted and a fresh `onPointerDown` arrives.
621
+ * This keeps `pointer` a single continuous stream with no mid-gesture
622
+ * teleport; read `touches` for anything beyond one finger.
623
+ */
585
624
  pointer = createPointer();
586
625
  /** Buffered keyboard state. Feed via `attachKeyboardListeners` (web) or `keyboard.keyDown`/`keyUp` (mobile/RN). */
587
626
  keyboard = createKeyboard();
588
627
  /** All active touch points. */
589
628
  touches = [];
590
629
  _touchMap = /* @__PURE__ */ new Map();
630
+ _primaryTouchId = null;
591
631
  _gamepads = [];
592
632
  _gamepadDeadZone = .15;
593
633
  _gamepadRemaps = /* @__PURE__ */ new Map();
594
634
  /** Call once per frame to reset per-frame flags. */
595
635
  update() {
596
636
  resetPointerFrame(this.pointer);
597
- resetKeyboardFrame(this.keyboard);
637
+ this.keyboard.reset();
598
638
  for (let i = 0; i < this._gamepads.length; i++) {
599
639
  const pad = this._gamepads[i];
600
640
  if (pad) resetGamepadFrame(pad);
@@ -602,17 +642,21 @@ var InputManager = class {
602
642
  }
603
643
  /** Feed a touch/pointer begin event. */
604
644
  onPointerDown(id, x, y) {
605
- pointerDown(this.pointer, x, y);
645
+ if (this._touchMap.size === 0) this._primaryTouchId = id;
646
+ if (id === this._primaryTouchId) pointerDown(this.pointer, x, y);
606
647
  this._addOrUpdateTouch(id, x, y, true);
607
648
  }
608
649
  /** Feed a touch/pointer move event. */
609
650
  onPointerMove(id, x, y) {
610
- pointerMove(this.pointer, x, y);
651
+ if (id === this._primaryTouchId) pointerMove(this.pointer, x, y);
611
652
  this._addOrUpdateTouch(id, x, y, true);
612
653
  }
613
654
  /** Feed a touch/pointer end event. */
614
655
  onPointerUp(id, x, y) {
615
- pointerUp(this.pointer, x, y);
656
+ if (id === this._primaryTouchId) {
657
+ pointerUp(this.pointer, x, y);
658
+ this._primaryTouchId = null;
659
+ }
616
660
  this._removeTouch(id);
617
661
  }
618
662
  /** Access the gamepad state at `index`. Allocates on first access. */
@@ -671,6 +715,16 @@ var InputManager = class {
671
715
  };
672
716
  //#endregion
673
717
  //#region src/virtual-joystick.ts
718
+ /**
719
+ * Dev-flag access for the config guard in `updateJoystick`, which runs once per
720
+ * frame while a stick is held. Read as a `globalThis` property, not as a bare
721
+ * `__DEV__` identifier: on JSC an unresolvable global costs ~270 ns per
722
+ * evaluation where a property miss is inline-cached at ~0.46 ns. Same reasoning
723
+ * as `@flare-engine/core`'s `Pool` — see PERF-10.
724
+ */
725
+ const G = globalThis;
726
+ /** Warn at most once per process; a bad config repeats every frame. */
727
+ let _warnedConfig = false;
674
728
  const DEFAULT_DEAD_ZONE = 10;
675
729
  const DEFAULT_MAX_RADIUS = 50;
676
730
  /** Create a default joystick state. */
@@ -699,27 +753,36 @@ function createJoystickState() {
699
753
  * @param config Joystick configuration.
700
754
  */
701
755
  function updateJoystick(state, centerX, centerY, touchX, touchY, config) {
702
- const deadZone = config?.deadZone ?? DEFAULT_DEAD_ZONE;
703
- const maxRadius = config?.maxRadius ?? DEFAULT_MAX_RADIUS;
756
+ let deadZone = config?.deadZone ?? DEFAULT_DEAD_ZONE;
757
+ let maxRadius = config?.maxRadius ?? DEFAULT_MAX_RADIUS;
758
+ if (!(deadZone >= 0 && maxRadius > deadZone)) {
759
+ if (G.__DEV__ && !_warnedConfig) {
760
+ _warnedConfig = true;
761
+ console.warn(`[flare-engine/input] updateJoystick: invalid config (deadZone=${deadZone}, maxRadius=${maxRadius}); expected 0 <= deadZone < maxRadius. Falling back to defaults for this call.`);
762
+ }
763
+ if (!(deadZone >= 0)) deadZone = DEFAULT_DEAD_ZONE;
764
+ if (!(maxRadius > deadZone)) maxRadius = deadZone + DEFAULT_MAX_RADIUS;
765
+ }
704
766
  const dx = touchX - centerX;
705
767
  const dy = touchY - centerY;
706
768
  const dist = Math.sqrt(dx * dx + dy * dy);
707
769
  state.active = true;
708
- if (dist < deadZone) {
770
+ if (!(dist >= deadZone)) {
709
771
  state.direction.x = 0;
710
772
  state.direction.y = 0;
711
773
  state.magnitude = 0;
712
- state.offset.x = dx;
713
- state.offset.y = dy;
774
+ const centred = dist === 0 || !Number.isFinite(dist);
775
+ state.offset.x = centred ? 0 : dx;
776
+ state.offset.y = centred ? 0 : dy;
714
777
  return;
715
778
  }
716
779
  const clampedDist = Math.min(dist, maxRadius);
717
780
  const normalizedDist = (clampedDist - deadZone) / (maxRadius - deadZone);
718
- const invDist = 1 / dist;
781
+ const invDist = dist > 0 ? 1 / dist : 0;
719
782
  state.direction.x = dx * invDist;
720
783
  state.direction.y = dy * invDist;
721
784
  state.magnitude = Math.min(normalizedDist, 1);
722
- const scale = clampedDist / dist;
785
+ const scale = dist > 0 ? clampedDist / dist : 0;
723
786
  state.offset.x = dx * scale;
724
787
  state.offset.y = dy * scale;
725
788
  }
@@ -758,7 +821,6 @@ exports.releaseButton = releaseButton;
758
821
  exports.releaseJoystick = releaseJoystick;
759
822
  exports.resetButtonFrame = resetButtonFrame;
760
823
  exports.resetGamepadFrame = resetGamepadFrame;
761
- exports.resetKeyboardFrame = resetKeyboardFrame;
762
824
  exports.resetPointerFrame = resetPointerFrame;
763
825
  exports.saveRemap = saveRemap;
764
826
  exports.updateGamepadFromSnapshot = updateGamepadFromSnapshot;