@yagejs/input 0.4.0 → 0.5.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.
@@ -0,0 +1,685 @@
1
+ import { Vec2, RendererAdapter, ServiceKey } from '@yagejs/core';
2
+
3
+ /** Central input state manager. Resolved via DI with InputManagerKey. */
4
+ declare class InputManager {
5
+ private pressedKeys;
6
+ private justPressedKeys;
7
+ private justReleasedKeys;
8
+ private holdStart;
9
+ private syntheticPressedActions;
10
+ private syntheticActionStarts;
11
+ private actionMap;
12
+ private defaultBindings;
13
+ private groups;
14
+ private actionGroups;
15
+ private disabledGroups;
16
+ /** Tracked pointers keyed by `pointerId`. Mouse persists; touch/pen removed on up/cancel. */
17
+ private pointers;
18
+ /** Id of the pointer the browser last marked `isPrimary`, or `null` when none are tracked. */
19
+ private primaryPointerId;
20
+ /**
21
+ * Aggregate "any pointer has this button held" cache. The action-map codes
22
+ * `MouseLeft`/`MouseMiddle`/`MouseRight` are driven from edges into/out of this
23
+ * set so two simultaneous taps holding button 0 do not double-fire.
24
+ * Consumed pointers are excluded from the aggregate so UI-claimed presses
25
+ * never propagate to gameplay actions.
26
+ */
27
+ private mouseButtonAggregate;
28
+ /**
29
+ * Pointers marked as "claimed" via {@link consumePointer} (or auto-claimed by
30
+ * the renderer's UI hit-test fallback). Lifetime is per-pointer-event-cycle:
31
+ * cleared when the pointer's last button releases (drained `pointerUp`) or on
32
+ * `pointercancel`.
33
+ */
34
+ private consumedPointers;
35
+ /** Wheel-edge gate flipped by {@link consumeWheel}. Cleared at end of frame. */
36
+ private consumedWheelThisFrame;
37
+ /** Buffered DOM-originated events awaiting drain at `Phase.EarlyUpdate`. */
38
+ private inputQueue;
39
+ /**
40
+ * Renderer reference for the optional `hitTestUI(x, y)` lookup. Stashed by
41
+ * {@link _setRenderer} during `InputPlugin.install` so the drain step can
42
+ * read it cheaply each frame.
43
+ */
44
+ private renderer;
45
+ private pointerDownListeners;
46
+ private pointerUpListeners;
47
+ private pointerMoveListeners;
48
+ private keyDownListenersAny;
49
+ private keyUpListenersAny;
50
+ private keyDownListeners;
51
+ private keyUpListeners;
52
+ private actionListeners;
53
+ private actionReleasedListeners;
54
+ private wheelListeners;
55
+ /** Real-pad axis values keyed by `${padIndex}:${axisKey}`. */
56
+ private gamepadAxisState;
57
+ /** Synthetic axis values for fireGamepadAxis injection (test path). */
58
+ private syntheticAxisState;
59
+ /** "Any pad" aggregate of currently-pressed gamepad codes. */
60
+ private lastButtonState;
61
+ /** Per-pad "anything happening" flag, used to detect rising-edge activity for active-pad promotion. */
62
+ private lastPadActivity;
63
+ /** Pads currently known to the engine (populated via events or polling). */
64
+ private connectedPads;
65
+ /** Index of the pad whose analog input is read by default. `null` when no pad is connected. */
66
+ private activePadIndex;
67
+ private gamepadConnectListeners;
68
+ private gamepadDisconnectListeners;
69
+ private activePadListeners;
70
+ private stickDeadzone;
71
+ private triggerDeadzone;
72
+ private triggerThreshold;
73
+ private pollingEnabled;
74
+ private camera;
75
+ private elapsedMs;
76
+ private listenResolve;
77
+ /** Whether any key mapped to this action is currently held. */
78
+ isPressed(action: string): boolean;
79
+ /** Whether any key mapped to this action was pressed this frame. */
80
+ isJustPressed(action: string): boolean;
81
+ /** Whether any key mapped to this action was released this frame. */
82
+ isJustReleased(action: string): boolean;
83
+ /** Returns true if any key bound to the action exists in the given set. */
84
+ private anyKeyInSet;
85
+ /** Milliseconds the action has been held. Returns 0 if not held. */
86
+ getHoldDuration(action: string): number;
87
+ /** Whether the action has been held for at least `minTime` ms. */
88
+ isHeldFor(action: string, minTime: number): boolean;
89
+ /** Returns -1, 0, or 1 based on negative/positive action states. */
90
+ getAxis(negative: string, positive: string): number;
91
+ /** Returns a Vec2 from four directional actions. Not normalized. */
92
+ getVector(left: string, right: string, up: string, down: string): Vec2;
93
+ /**
94
+ * Primary pointer's position in world coordinates (via Camera), or screen
95
+ * coords if no camera. Returns `Vec2.ZERO` when no pointer is tracked.
96
+ *
97
+ * For multi-pointer access (touch UIs etc.) iterate {@link getPointers} and
98
+ * convert each `screenPos` via the camera as needed.
99
+ */
100
+ getPointerPosition(): Vec2;
101
+ /** Primary pointer's raw position in screen coordinates, or `Vec2.ZERO` when no pointer is tracked. */
102
+ getPointerScreenPosition(): Vec2;
103
+ /** Whether the primary pointer has any button held. */
104
+ isPointerDown(): boolean;
105
+ /** All currently-tracked pointers (one per active mouse, pen, or finger). */
106
+ getPointers(): readonly PointerInfo[];
107
+ /** Direct lookup by `pointerId`, or `undefined` if no pointer with that id is tracked. */
108
+ getPointer(id: number): PointerInfo | undefined;
109
+ /**
110
+ * Defensive snapshot of a tracked pointer. The runtime `MutablePointerInfo`
111
+ * holds a real `Set` for `buttons` — even though the `PointerInfo` type
112
+ * declares `ReadonlySet`, JS doesn't enforce that at runtime, so we copy the
113
+ * set on every public read. `Vec2` is convention-immutable across YAGE, so
114
+ * we share the same instance.
115
+ */
116
+ private toPointerInfo;
117
+ /**
118
+ * Subscribe to pointer-down events (button transitions from up → down on a
119
+ * tracked pointer). Returns a disposer that detaches the listener.
120
+ */
121
+ onPointerDown(fn: (info: PointerInfo) => void): () => void;
122
+ /**
123
+ * Subscribe to pointer-up events (button transitions from down → up, plus
124
+ * touch / pen lifecycle ends and `pointercancel`). Returns a disposer.
125
+ */
126
+ onPointerUp(fn: (info: PointerInfo) => void): () => void;
127
+ /** Subscribe to pointer-move events. Returns a disposer. */
128
+ onPointerMove(fn: (info: PointerInfo) => void): () => void;
129
+ /**
130
+ * Mark a pointer as claimed for the rest of its event cycle (down → up).
131
+ * Subsequent action-map edges for this pointer (e.g. the `MouseLeft` edge a
132
+ * `pointerdown` would normally fire) are suppressed; `onPointerDown/Up/Move`
133
+ * listeners still fire because they are explicit user opt-ins.
134
+ *
135
+ * The mark clears automatically when the pointer's last button releases or
136
+ * on `pointercancel`. Call from a Pixi `pointerdown` handler that wants to
137
+ * own the event: `manager.consumePointer(e.pointerId)`.
138
+ */
139
+ consumePointer(id: number): void;
140
+ /** Whether the pointer is currently marked consumed. */
141
+ isPointerConsumed(id: number): boolean;
142
+ /**
143
+ * Suppress wheel action-map edges (`WheelUp/Down/Left/Right`) for the rest
144
+ * of the current frame. `onWheel` listeners still fire.
145
+ */
146
+ consumeWheel(): void;
147
+ /**
148
+ * Subscribe to key-down events. Pass a code (e.g. `"Space"`, `"GamepadA"`)
149
+ * to filter, or `"*"` for all keys. The listener fires on the same edge
150
+ * `isJustPressed` reports — for DOM-originated events that's the next
151
+ * `Phase.EarlyUpdate` after the browser dispatches; for synthetic injection
152
+ * (`fireKeyDown`) it's synchronous. Returns a disposer.
153
+ */
154
+ onKeyDown(code: string, fn: (code: string) => void): () => void;
155
+ /** Subscribe to key-up events. See {@link onKeyDown}. */
156
+ onKeyUp(code: string, fn: (code: string) => void): () => void;
157
+ /**
158
+ * Subscribe to action press edges (rising edge of any key bound to the
159
+ * action). Fires once per press. Returns a disposer.
160
+ */
161
+ onAction(name: string, fn: (name: string) => void): () => void;
162
+ /** Subscribe to action release edges. Returns a disposer. */
163
+ onActionReleased(name: string, fn: (name: string) => void): () => void;
164
+ /**
165
+ * Subscribe to scroll-wheel events. Receives raw `deltaX`/`deltaY` (already
166
+ * sign-flipped by `InputConfig.wheelInvertY` if set). Fires regardless of
167
+ * {@link consumeWheel} — it only gates action edges. Returns a disposer.
168
+ */
169
+ onWheel(fn: (dx: number, dy: number) => void): () => void;
170
+ private getPrimaryPointer;
171
+ /** Replace the entire action map and store it as the default for {@link resetBindings}. */
172
+ setActionMap(actions: ActionMapDefinition): void;
173
+ /** Add a key binding to an action. Creates the action if it doesn't exist. */
174
+ bindKey(action: string, key: string): void;
175
+ /** Remove a key binding from an action. */
176
+ unbindKey(action: string, key: string): void;
177
+ /** Returns the current key bindings for an action, or an empty array if unmapped. */
178
+ getBindings(action: string): readonly string[];
179
+ /** Returns all action names that have the given key bound. */
180
+ getActionsForKey(key: string): string[];
181
+ /**
182
+ * Rebind a key to an action with optional conflict detection.
183
+ * Conflicts are only detected between actions sharing at least one group.
184
+ */
185
+ rebind(action: string, key: string, opts?: RebindOptions): RebindResult;
186
+ /**
187
+ * Finds the first action that uses the given key AND shares at least one
188
+ * group with the target action. Ungrouped actions never conflict.
189
+ */
190
+ private findConflictInGroups;
191
+ /** Reset bindings to defaults. If an action name is provided, only reset that action. */
192
+ resetBindings(action?: string): void;
193
+ /** Export the current bindings as a plain object for serialization. */
194
+ exportBindings(): ActionMapDefinition;
195
+ /** Load bindings from a plain object. Resets to defaults first, then overlays the provided map. */
196
+ loadBindings(map: ActionMapDefinition): void;
197
+ /** Configure input groups. Group name -> array of action names. */
198
+ setGroups(groups: Record<string, string[]>): void;
199
+ /** Enable a group by name. */
200
+ enableGroup(name: string): void;
201
+ /** Disable a group by name. Actions only in disabled groups become inactive. */
202
+ disableGroup(name: string): void;
203
+ /** Set exactly these groups as active; all others are disabled. */
204
+ setActiveGroups(names: string[]): void;
205
+ /** Whether a group is currently enabled. Returns true for unknown group names. */
206
+ isGroupEnabled(name: string): boolean;
207
+ /** Get all configured group names. */
208
+ getGroups(): string[];
209
+ /** Get the action names belonging to a group. Returns empty array for unknown groups. */
210
+ getGroupActions(name: string): readonly string[];
211
+ /** Returns true if the action is ungrouped or any of its groups is enabled. */
212
+ private isActionEnabled;
213
+ /** Returns a promise that resolves with the next key code pressed. Intercepts the key. */
214
+ listenForNextKey(): Promise<string | null>;
215
+ /** Cancel an active {@link listenForNextKey}. Resolves the pending promise with `null`. */
216
+ cancelListen(): void;
217
+ /** Public wrapper for synthetic key-down injection. Applies sync. */
218
+ fireKeyDown(code: string): void;
219
+ /** Public wrapper for synthetic key-up injection. Applies sync. */
220
+ fireKeyUp(code: string): void;
221
+ /**
222
+ * Public wrapper for synthetic pointer movement. Defaults to the primary
223
+ * mouse pointer (`id: 1`, `type: "mouse"`); pass `opts` to drive a specific
224
+ * touch / pen pointer.
225
+ */
226
+ firePointerMove(screenX: number, screenY: number, opts?: {
227
+ id?: number;
228
+ type?: PointerType;
229
+ isPrimary?: boolean;
230
+ }): void;
231
+ /**
232
+ * Public wrapper for synthetic pointer-button presses. Defaults to button 0
233
+ * on the primary mouse pointer. Pass `opts` for touch / pen / non-primary
234
+ * pointers (e.g. `{ id: 5, type: "touch", isPrimary: false }`).
235
+ */
236
+ firePointerDown(button?: 0 | 1 | 2, opts?: {
237
+ id?: number;
238
+ type?: PointerType;
239
+ isPrimary?: boolean;
240
+ }): void;
241
+ /** Public wrapper for synthetic pointer-button releases. */
242
+ firePointerUp(button?: 0 | 1 | 2, opts?: {
243
+ id?: number;
244
+ }): void;
245
+ /** Public wrapper for synthetic wheel input. Applies sync, including
246
+ * action edges and `onWheel` listener notification — matching the DOM path
247
+ * so tests and inspector probes drive the full surface. */
248
+ fireWheel(dx: number, dy: number): void;
249
+ private makeSyntheticInfo;
250
+ /**
251
+ * Inject a synthetic gamepad button edge. Routes through the same internal
252
+ * path as real polling, so action queries (`isPressed`, `isJustPressed`),
253
+ * `listenForNextKey`, and rebinding all see the synthetic input.
254
+ *
255
+ * `code` should be a gamepad code string (e.g. `"GamepadA"`, `"GamepadLT"`).
256
+ * Used by inspector probes / deterministic tests in lieu of real polling.
257
+ */
258
+ fireGamepadButton(code: string, pressed: boolean): void;
259
+ /**
260
+ * Inject a synthetic gamepad axis value. Stored separately from real-pad
261
+ * axis state and consulted by `getStick` / `getTrigger` only when no real
262
+ * pad is active — matching how a test fixture would use the API.
263
+ *
264
+ * Trigger axes additionally emit `GamepadLT`/`GamepadRT` button edges when
265
+ * crossing `triggerThreshold`, mirroring real-pad polling so synthetic
266
+ * inspector probes drive `isPressed` the same way as physical hardware.
267
+ */
268
+ fireGamepadAxis(side: GamepadAxisKey, value: number): void;
269
+ /**
270
+ * Returns the deadzoned, magnitude-clamped stick vector for the given side.
271
+ *
272
+ * By default reads from the active pad (the most recently used controller,
273
+ * or the first connected one if nothing has been used yet). Pass
274
+ * `{ pad: index }` to read from a specific pad — useful for couch-co-op
275
+ * where each player's controller is addressed explicitly.
276
+ *
277
+ * Falls back to synthetic injection (`fireGamepadAxis`) when no pad is
278
+ * active — that's the test/probe path.
279
+ */
280
+ getStick(side: "left" | "right", opts?: {
281
+ pad?: number;
282
+ }): Vec2;
283
+ /**
284
+ * Returns the deadzoned trigger value (0..1) for the given side.
285
+ * Reads from the active pad by default; use `{ pad: index }` for explicit
286
+ * per-pad reads. Falls back to synthetic state when no pad is active.
287
+ */
288
+ getTrigger(side: "left" | "right", opts?: {
289
+ pad?: number;
290
+ }): number;
291
+ /**
292
+ * Synchronously poll `navigator.getGamepads()` for currently-connected pads.
293
+ * Use this rather than the cached event-driven list when you need ground
294
+ * truth — `gamepadconnected` doesn't fire until the user presses a button.
295
+ */
296
+ gamepads(): readonly GamepadInfo[];
297
+ /**
298
+ * Subscribe to gamepad-connected events. Replays currently-known pads
299
+ * synchronously so callers don't need a separate `gamepads()` call.
300
+ * Returns a disposer.
301
+ */
302
+ onGamepadConnected(fn: (info: GamepadInfo) => void): () => void;
303
+ /** Subscribe to gamepad-disconnected events. Returns a disposer. */
304
+ onGamepadDisconnected(fn: (info: GamepadInfo) => void): () => void;
305
+ /**
306
+ * The pad whose analog input is read by default. Auto-promotes on input
307
+ * activity (button press or stick/trigger above deadzone) and on first
308
+ * connect. Returns `null` when no pad is connected.
309
+ */
310
+ getActivePad(): GamepadInfo | null;
311
+ /**
312
+ * Manually set the active pad. Index must match a currently connected pad
313
+ * — pass an unknown index and the call is a no-op. Pass `null` to clear
314
+ * (analog reads will fall back to synthetic state if any).
315
+ */
316
+ setActivePad(index: number | null): void;
317
+ /**
318
+ * Subscribe to active-pad changes. Replays the current active pad
319
+ * synchronously on subscribe so callers get the present state without a
320
+ * separate `getActivePad()` call. Returns a disposer.
321
+ */
322
+ onActivePadChanged(fn: (info: GamepadInfo | null) => void): () => void;
323
+ private setActivePadInternal;
324
+ /** Enable or disable real gamepad polling. Synthetic injection still works when disabled. */
325
+ setPollingEnabled(enabled: boolean): void;
326
+ /** Whether real gamepad polling is currently enabled. */
327
+ isPollingEnabled(): boolean;
328
+ /**
329
+ * Update analog deadzones at runtime. Either field may be omitted.
330
+ * Values are clamped to `[0, 0.999]` — capping below 1 keeps the rescaling
331
+ * denominator non-zero. Non-finite values are ignored.
332
+ */
333
+ setDeadzones(opts: {
334
+ stick?: number;
335
+ trigger?: number;
336
+ }): void;
337
+ /**
338
+ * Set the trigger button-edge threshold (default 0.5). Clamped to `[0, 1]`;
339
+ * non-finite values are ignored.
340
+ */
341
+ setTriggerThreshold(value: number): void;
342
+ /**
343
+ * @internal Force-release held gamepad buttons and clear real-pad analog
344
+ * snapshots. Used on tab-hide (where `navigator.getGamepads()` returns
345
+ * stale data) and on disconnect when polling is paused. Synthetic axes
346
+ * live in their own field, so they're untouched.
347
+ */
348
+ _releaseAllGamepadState(): void;
349
+ /** @internal Called by InputPlugin from `gamepadconnected` event or by
350
+ * polling when discovering a previously-unknown pad. Idempotent. */
351
+ _onGamepadConnected(info: GamepadInfo): void;
352
+ /** @internal Called by InputPlugin from `gamepaddisconnected` event or by
353
+ * polling when a pad vanishes silently. Idempotent. */
354
+ _onGamepadDisconnected(info: GamepadInfo): void;
355
+ /**
356
+ * @internal Poll real gamepads via `navigator.getGamepads()` and emit
357
+ * key-down/key-up edges for any aggregate state changes. Called by
358
+ * `InputPollSystem` once per frame.
359
+ */
360
+ _pollGamepads(): void;
361
+ /** Whether a pad has any input that should claim active-pad ownership. */
362
+ private padHasActivity;
363
+ /**
364
+ * Aggregate "any pad pressed" per code across the supplied pad list and
365
+ * emit `_applyKeyDown`/`_applyKeyUp` edges. `lastButtonState` is updated
366
+ * unconditionally so listen-mode interception doesn't cause held-button
367
+ * re-fires on subsequent frames.
368
+ */
369
+ private reconcileButtonStateAcrossPads;
370
+ /** Inject a one-frame synthetic action pulse. */
371
+ fireAction(name: string): void;
372
+ /** Release all synthetic and physical input state. */
373
+ clearAll(): void;
374
+ /**
375
+ * Drop all tracked pointers and release the aggregate `MouseLeft/Middle/Right`
376
+ * codes without touching keyboard or gamepad state. Useful for window-blur
377
+ * / page-hide handling.
378
+ */
379
+ clearPointerButtons(): void;
380
+ /** Snapshot of current held input state for inspector tooling. */
381
+ snapshotState(): {
382
+ keys: string[];
383
+ actions: string[];
384
+ mouse: {
385
+ x: number;
386
+ y: number;
387
+ buttons: number[];
388
+ down: boolean;
389
+ };
390
+ pointers: Array<{
391
+ id: number;
392
+ x: number;
393
+ y: number;
394
+ type: PointerType;
395
+ isPrimary: boolean;
396
+ buttons: number[];
397
+ down: boolean;
398
+ }>;
399
+ gamepad: {
400
+ buttons: string[];
401
+ axes: Array<{
402
+ key: string;
403
+ value: number;
404
+ }>;
405
+ };
406
+ };
407
+ /**
408
+ * @internal Stash the renderer adapter so the drain step can call its
409
+ * optional `hitTestUI(x, y)` for the auto-consume fallback. Called by
410
+ * `InputPlugin.install`.
411
+ */
412
+ _setRenderer(renderer: RendererAdapter | null): void;
413
+ /** @internal */
414
+ _enqueueKeyDown(code: string): void;
415
+ /** @internal */
416
+ _enqueueKeyUp(code: string): void;
417
+ /**
418
+ * @internal Sync portion: upsert the pointer entry (existence, screenPos,
419
+ * type, isPrimary, primaryPointerId) and notify pointerMoveListeners so
420
+ * pointer-tracking UIs see live cursor positions. Move events do not carry
421
+ * action-map edges, so they are not queued.
422
+ */
423
+ _enqueuePointerMove(info: PointerEventInfo): void;
424
+ /**
425
+ * @internal Sync portion: upsert pointer (existence, screenPos, type,
426
+ * isPrimary, primaryPointerId) and notify pointerDownListeners. Button
427
+ * mutation, action-map edges, and mouse-aggregate emit are deferred to the
428
+ * next drain at `Phase.EarlyUpdate` so {@link consumePointer} (or the
429
+ * renderer's UI hit-test) can suppress them, AND so a same-frame
430
+ * down+up that arrives before drain still produces the correct
431
+ * `MouseLeft` press/release edges (recomputing aggregate from live state
432
+ * after sync mutation would silently drop the transient transition).
433
+ *
434
+ * Listeners therefore observe `pointer.buttons` BEFORE this event's edge is
435
+ * applied. That's a documented tradeoff: the canonical event-button info
436
+ * is in the `FederatedPointerEvent` / `PointerEvent` the user's Pixi
437
+ * handler already receives, so the lossy `info.buttons` view rarely
438
+ * matters in practice.
439
+ */
440
+ _enqueuePointerDown(info: PointerEventInfo): void;
441
+ /** @internal */
442
+ _enqueuePointerUp(info: PointerEventInfo): void;
443
+ /** @internal */
444
+ _enqueuePointerCancel(id: number): void;
445
+ /** @internal */
446
+ _enqueueWheel(dx: number, dy: number): void;
447
+ /**
448
+ * @internal Drain queued DOM events at `Phase.EarlyUpdate`. Each event
449
+ * applies its deferred state (button mutations, action-map edges,
450
+ * mouse-aggregate transitions). Consumed pointers are excluded from the
451
+ * mouse aggregate so UI-claimed presses do not propagate to gameplay
452
+ * actions. The renderer's optional `hitTestUI(x, y)` auto-claims a pointer
453
+ * whose `pointerdown` lands on a UI-marked container.
454
+ */
455
+ _drainInputQueue(): void;
456
+ private drainPointerDown;
457
+ private drainPointerUp;
458
+ private drainPointerCancel;
459
+ private applyWheelEdges;
460
+ /**
461
+ * Add a code to `justPressedKeys` without entering `pressedKeys`. Used for
462
+ * discrete edges (wheel ticks) that are never "held". Listeners and
463
+ * `listenForNextKey` still fire as usual.
464
+ */
465
+ private fireOneFrameEdge;
466
+ /**
467
+ * @internal Synthetic key-down. DOM-originated events must use
468
+ * {@link _enqueueKeyDown} so `consumePointer` and the UI hit-test fallback
469
+ * have a chance to run before action edges fire.
470
+ */
471
+ _applyKeyDown(code: string): void;
472
+ /**
473
+ * @internal Synthetic key-up. DOM-originated events must use
474
+ * {@link _enqueueKeyUp}.
475
+ */
476
+ _applyKeyUp(code: string): void;
477
+ /**
478
+ * @internal Synthetic pointer move. DOM-originated events must use
479
+ * {@link _enqueuePointerMove}.
480
+ */
481
+ _applyPointerMove(info: PointerEventInfo): void;
482
+ /**
483
+ * @internal Synthetic pointer down. DOM-originated events must use
484
+ * {@link _enqueuePointerDown}. This applies all state (button mutation,
485
+ * mouse-aggregate emit, listener notify) synchronously.
486
+ */
487
+ _applyPointerDown(info: PointerEventInfo): void;
488
+ /**
489
+ * @internal Synthetic pointer up. DOM-originated events must use
490
+ * {@link _enqueuePointerUp}.
491
+ */
492
+ _applyPointerUp(info: PointerEventInfo): void;
493
+ /**
494
+ * @internal Synthetic pointer cancel. Clears all buttons on the pointer,
495
+ * fires up-listeners, and drops the entry (unless it's a mouse). Mirrors
496
+ * the drain-time {@link drainPointerCancel} logic.
497
+ */
498
+ _applyPointerCancel(id: number): void;
499
+ private upsertPointer;
500
+ private removePointer;
501
+ /**
502
+ * Recompute the `MouseLeft/Middle/Right` aggregate edge for `button`.
503
+ * Consumed pointers are excluded so a UI-claimed press never propagates to
504
+ * gameplay actions, even if a second non-UI pointer simultaneously holds
505
+ * the same button.
506
+ */
507
+ private recomputeMouseAggregate;
508
+ private notifyPointerListeners;
509
+ private notifyKeyListeners;
510
+ private notifyActionListeners;
511
+ /**
512
+ * Action names that include `code` in their bindings AND whose group is
513
+ * currently enabled. Used for `onAction` / `onActionReleased` listener
514
+ * fan-out so disabled-group suppression matches `isPressed` behavior.
515
+ */
516
+ private actionsForCode;
517
+ /** @internal Clear per-frame justPressed/justReleased flags. */
518
+ _clearFrameState(): void;
519
+ /** Set camera for pointer world-coord conversion. */
520
+ setCamera(camera: CameraLike): void;
521
+ /** Clear the camera reference (e.g. on scene exit). */
522
+ clearCamera(): void;
523
+ /** Get all configured action names. */
524
+ getActionNames(): string[];
525
+ /** @internal Advance the elapsed game-time clock. Called by InputPollSystem. */
526
+ _advanceTime(dtMs: number): void;
527
+ /** @internal Sync alias — see {@link _applyKeyDown}. */
528
+ _onKeyDown(code: string): void;
529
+ /** @internal Sync alias — see {@link _applyKeyUp}. */
530
+ _onKeyUp(code: string): void;
531
+ /** @internal Sync alias — see {@link _applyPointerMove}. */
532
+ _onPointerMove(info: PointerEventInfo): void;
533
+ /** @internal Sync alias — see {@link _applyPointerDown}. */
534
+ _onPointerDown(info: PointerEventInfo): void;
535
+ /** @internal Sync alias — see {@link _applyPointerUp}. */
536
+ _onPointerUp(info: PointerEventInfo): void;
537
+ /** @internal Sync alias — see {@link _applyPointerCancel}. */
538
+ _onPointerCancel(id: number): void;
539
+ }
540
+
541
+ /** Service key for the InputManager. */
542
+ declare const InputManagerKey: ServiceKey<InputManager>;
543
+ /** Minimal camera surface needed by InputManager for pointer world-coord conversion. */
544
+ interface CameraLike {
545
+ screenToWorld(screenX: number, screenY: number): {
546
+ x: number;
547
+ y: number;
548
+ };
549
+ }
550
+ /**
551
+ * Minimal renderer surface needed by InputPlugin for canvas access and
552
+ * coordinate mapping. Alias of the cross-package `RendererAdapter` contract.
553
+ */
554
+ type RendererLike = RendererAdapter;
555
+ /** Configuration for the InputPlugin. */
556
+ interface InputConfig {
557
+ /** Target element for pointer events (default: canvas from renderer, or document). */
558
+ target?: HTMLElement;
559
+ /** Action map: action name -> array of physical key codes. */
560
+ actions?: ActionMapDefinition;
561
+ /** Input groups: group name -> array of action names belonging to it. */
562
+ groups?: Record<string, string[]>;
563
+ /** Key codes to call preventDefault() on (default: none). */
564
+ preventDefaultKeys?: string[];
565
+ /**
566
+ * Optional override for the renderer service key. When omitted, InputPlugin
567
+ * auto-resolves `RendererAdapterKey` — the canonical `@yagejs/renderer`
568
+ * plugin registers itself under that key, so pointer events target its
569
+ * canvas and coordinates route through `canvasToVirtual` out of the box.
570
+ * Set this only if you ship a custom renderer registered under a different
571
+ * key.
572
+ */
573
+ rendererKey?: ServiceKey<RendererAdapter>;
574
+ /** Deadzone thresholds for analog inputs. */
575
+ deadzones?: {
576
+ /** Radial deadzone applied to stick magnitude (default 0.15). */
577
+ stick?: number;
578
+ /** Lower deadzone for trigger analog values (default 0.05). */
579
+ trigger?: number;
580
+ };
581
+ /**
582
+ * Trigger value at which `GamepadLT`/`GamepadRT` fire as button edges in the
583
+ * action map (default 0.5). Below this, the trigger remains "released" for
584
+ * `isPressed` purposes; the analog `getTrigger` value is unaffected.
585
+ */
586
+ triggerThreshold?: number;
587
+ /**
588
+ * Whether to poll `navigator.getGamepads()` each frame (default `true`).
589
+ * Disable to use only synthetic input via `fireGamepadButton`/`fireGamepadAxis`
590
+ * — useful for inspector probes that want deterministic state.
591
+ */
592
+ pollGamepads?: boolean;
593
+ /**
594
+ * Invert vertical scroll so positive `dy` means up (default `false`,
595
+ * matching the W3C convention where positive `deltaY` is "scroll content
596
+ * down"). Affects both `onWheel` callbacks and `WheelUp/Down` action edges.
597
+ */
598
+ wheelInvertY?: boolean;
599
+ /**
600
+ * Call `preventDefault()` on incoming wheel events so the page does not
601
+ * scroll. Default `false` — opt in only if your game canvas should swallow
602
+ * scroll. The listener is attached as `{ passive: false }` when this is
603
+ * enabled so `preventDefault()` actually takes effect.
604
+ */
605
+ preventDefaultWheel?: boolean;
606
+ }
607
+ /** Information about a connected gamepad. */
608
+ interface GamepadInfo {
609
+ /** Index in `navigator.getGamepads()`. May change if pads are hot-swapped. */
610
+ index: number;
611
+ /** Browser-reported gamepad identifier (vendor + product). */
612
+ id: string;
613
+ }
614
+ /**
615
+ * Named gamepad analog axis. Sticks are exposed per axis (`leftX`/`leftY`,
616
+ * etc.); triggers (`leftTrigger`/`rightTrigger`) carry the W3C
617
+ * `GamepadButton.value` for buttons 6/7 under standard mapping.
618
+ */
619
+ type GamepadAxisKey = "leftX" | "leftY" | "rightX" | "rightY" | "leftTrigger" | "rightTrigger";
620
+ /**
621
+ * Class of physical pointer device. Sourced from `PointerEvent.pointerType` on
622
+ * real input; defaults to `"mouse"` for synthetic injection.
623
+ */
624
+ type PointerType = "mouse" | "pen" | "touch";
625
+ /**
626
+ * Read-only view of a tracked pointer. Returned from {@link InputManager.getPointers}
627
+ * and the per-pointer event hooks. Treat as immutable — fields reflect the
628
+ * pointer's state at query time and are not retained between frames.
629
+ */
630
+ interface PointerInfo {
631
+ /** Browser-assigned `PointerEvent.pointerId`, or the synthetic id passed via `firePointer*`. */
632
+ readonly id: number;
633
+ /** Position in screen-space pixels (already routed through `canvasToVirtual` if available). */
634
+ readonly screenPos: Vec2;
635
+ /** Source device class. */
636
+ readonly type: PointerType;
637
+ /** Whether the browser flagged this as the primary pointer (`PointerEvent.isPrimary`). */
638
+ readonly isPrimary: boolean;
639
+ /** Currently-held button indices (0=left/primary, 1=middle, 2=right). */
640
+ readonly buttons: ReadonlySet<number>;
641
+ /** Convenience mirror of `buttons.size > 0`. */
642
+ readonly isDown: boolean;
643
+ }
644
+ /**
645
+ * Per-event payload assembled by {@link InputPlugin} from each `PointerEvent`
646
+ * (or by `firePointer*` for synthetic injection) and forwarded to the manager's
647
+ * internal pointer handlers.
648
+ *
649
+ * @internal
650
+ */
651
+ interface PointerEventInfo {
652
+ id: number;
653
+ screenX: number;
654
+ screenY: number;
655
+ type: PointerType;
656
+ isPrimary: boolean;
657
+ /**
658
+ * The button whose state changed for this event. `-1` for events that don't
659
+ * change button state (move-only). Down/up handlers ignore `-1`.
660
+ */
661
+ button: number;
662
+ }
663
+ /** Maps action names to arrays of physical key codes. */
664
+ type ActionMapDefinition = Record<string, string[]>;
665
+ /** How to handle a conflict when rebinding a key already used by another action in the same group. */
666
+ type InputConflictPolicy = "replace" | "keep-both" | "reject";
667
+ /** Options for {@link InputManager.rebind}. */
668
+ interface RebindOptions {
669
+ /** Index of the binding slot to replace. Appends if the slot does not exist. */
670
+ slot?: number;
671
+ /** How to resolve conflicts with other actions in the same group(s). Default: `"reject"`. */
672
+ conflict?: InputConflictPolicy;
673
+ }
674
+ /** Result of a {@link InputManager.rebind} call. */
675
+ interface RebindResult {
676
+ /** Whether the rebind succeeded. */
677
+ ok: boolean;
678
+ /** Present when `ok` is false due to a conflict with `conflict: "reject"`. */
679
+ conflict?: {
680
+ action: string;
681
+ key: string;
682
+ };
683
+ }
684
+
685
+ export { type ActionMapDefinition as A, type CameraLike as C, type GamepadAxisKey as G, type InputConfig as I, type PointerInfo as P, type RebindOptions as R, type GamepadInfo as a, type InputConflictPolicy as b, InputManager as c, InputManagerKey as d, type PointerType as e, type RebindResult as f, type RendererLike as g };