@flighthq/input 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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.
@@ -47,182 +47,29 @@ export function applyGamepadStickDeadZone(out, x, y, deadZone) {
47
47
  out.x = x * scale;
48
48
  out.y = y * scale;
49
49
  }
50
- export function attachGamepadInput(manager, target, options) {
51
- const onGamepadConnected = (e) => {
52
- if (!manager.enabled)
53
- return;
54
- const pad = e.gamepad;
55
- const prev = getOrCreateGamepadPollState(manager);
56
- prev.axes.set(pad.index, Array.from(pad.axes));
57
- prev.buttons.set(pad.index, Array.from(pad.buttons, (b) => b.pressed));
58
- _connectData.gamepad = pad.index;
59
- _connectData.id = pad.id;
60
- _connectData.mapping = pad.mapping === 'standard' ? 'standard' : pad.mapping === '' ? '' : 'raw';
61
- emitSignal(manager.onGamepadConnect, _connectData);
62
- };
63
- const onGamepadDisconnected = (e) => {
64
- if (!manager.enabled)
65
- return;
66
- const pad = e.gamepad;
67
- const prev = getOrCreateGamepadPollState(manager);
68
- prev.axes.delete(pad.index);
69
- prev.buttons.delete(pad.index);
70
- _connectData.gamepad = pad.index;
71
- _connectData.id = pad.id;
72
- _connectData.mapping = pad.mapping === 'standard' ? 'standard' : pad.mapping === '' ? '' : 'raw';
73
- emitSignal(manager.onGamepadDisconnect, _connectData);
74
- };
75
- let rafId = 0;
76
- const loop = () => {
77
- pollGamepadInput(manager);
78
- rafId = requestAnimationFrame(loop);
79
- };
80
- target.addEventListener('gamepadconnected', onGamepadConnected);
81
- target.addEventListener('gamepaddisconnected', onGamepadDisconnected);
82
- rafId = requestAnimationFrame(loop);
83
- setInputBinding(manager, target, kGamepadInput, () => {
84
- target.removeEventListener('gamepadconnected', onGamepadConnected);
85
- target.removeEventListener('gamepaddisconnected', onGamepadDisconnected);
86
- cancelAnimationFrame(rafId);
87
- });
88
- // Suppress unused-options warning; options accepted for API symmetry.
89
- void options;
50
+ export function attachGamepadInput(manager, source, options) {
51
+ const release = getInputIngressBackend().attachGamepad(source, getInputIngressSink(manager), options);
52
+ setInputBinding(manager, source, kGamepadInput, release);
90
53
  }
91
- export function attachKeyboardInput(manager, target, options) {
92
- const preventDefault = options?.preventDefault ?? true;
93
- const onKeyDown = (e) => {
94
- if (!manager.enabled)
95
- return;
96
- const ke = e;
97
- if (preventDefault)
98
- ke.preventDefault();
99
- setInputKeyboardData(_keyboardData, ke);
100
- emitSignal(manager.onKeyDown, _keyboardData);
101
- };
102
- const onKeyUp = (e) => {
103
- if (!manager.enabled)
104
- return;
105
- const ke = e;
106
- if (preventDefault)
107
- ke.preventDefault();
108
- setInputKeyboardData(_keyboardData, ke);
109
- emitSignal(manager.onKeyUp, _keyboardData);
110
- };
111
- target.addEventListener('keydown', onKeyDown);
112
- target.addEventListener('keyup', onKeyUp);
113
- setInputBinding(manager, target, kKeyboardInput, () => {
114
- target.removeEventListener('keydown', onKeyDown);
115
- target.removeEventListener('keyup', onKeyUp);
116
- });
54
+ export function attachKeyboardInput(manager, source, options) {
55
+ const release = getInputIngressBackend().attachKeyboard(source, getInputIngressSink(manager), options);
56
+ setInputBinding(manager, source, kKeyboardInput, release);
117
57
  }
118
- export function attachPointerInput(manager, element, options) {
119
- const preventDefault = options?.preventDefault ?? true;
120
- const onContextMenu = (e) => {
121
- if (preventDefault)
122
- e.preventDefault();
123
- };
124
- const onPointerCancel = (e) => {
125
- if (!manager.enabled)
126
- return;
127
- if (preventDefault)
128
- e.preventDefault();
129
- setInputPointerData(_pointerData, e, 0, 0);
130
- emitSignal(manager.onPointerCancel, _pointerData);
131
- };
132
- const onPointerDown = (e) => {
133
- if (!manager.enabled)
134
- return;
135
- if (preventDefault)
136
- e.preventDefault();
137
- setInputPointerData(_pointerData, e, 0, 0);
138
- emitSignal(manager.onPointerDown, _pointerData);
139
- };
140
- const onPointerMove = (e) => {
141
- if (!manager.enabled)
142
- return;
143
- if (preventDefault)
144
- e.preventDefault();
145
- setInputPointerData(_pointerData, e, 0, 0);
146
- emitSignal(manager.onPointerMove, _pointerData);
147
- };
148
- const onPointerUp = (e) => {
149
- if (!manager.enabled)
150
- return;
151
- if (preventDefault)
152
- e.preventDefault();
153
- setInputPointerData(_pointerData, e, 0, 0);
154
- emitSignal(manager.onPointerUp, _pointerData);
155
- };
156
- element.addEventListener('contextmenu', onContextMenu);
157
- element.addEventListener('pointercancel', onPointerCancel);
158
- element.addEventListener('pointerdown', onPointerDown);
159
- element.addEventListener('pointermove', onPointerMove);
160
- element.addEventListener('pointerup', onPointerUp);
161
- setInputBinding(manager, element, kPointerInput, () => {
162
- element.removeEventListener('contextmenu', onContextMenu);
163
- element.removeEventListener('pointercancel', onPointerCancel);
164
- element.removeEventListener('pointerdown', onPointerDown);
165
- element.removeEventListener('pointermove', onPointerMove);
166
- element.removeEventListener('pointerup', onPointerUp);
167
- });
58
+ export function attachPointerInput(manager, source, options) {
59
+ const release = getInputIngressBackend().attachPointer(source, getInputIngressSink(manager), options);
60
+ setInputBinding(manager, source, kPointerInput, release);
168
61
  }
169
- export function attachRelativePointerInput(manager, element, options) {
170
- const preventDefault = options?.preventDefault ?? true;
171
- const target = element.ownerDocument;
172
- const handler = (e) => {
173
- if (!manager.enabled)
174
- return;
175
- const me = e;
176
- if (preventDefault)
177
- me.preventDefault();
178
- setInputPointerData(_pointerData, me, me.movementX, me.movementY);
179
- emitSignal(manager.onPointerMoveRelative, _pointerData);
180
- };
181
- target.addEventListener('mousemove', handler);
182
- setInputBinding(manager, element, kRelativePointerInput, () => target.removeEventListener('mousemove', handler));
62
+ export function attachRelativePointerInput(manager, source, options) {
63
+ const release = getInputIngressBackend().attachRelativePointer(source, getInputIngressSink(manager), options);
64
+ setInputBinding(manager, source, kRelativePointerInput, release);
183
65
  }
184
- export function attachTextInput(manager, element, options) {
185
- const onBeforeInput = (e) => {
186
- if (!manager.enabled)
187
- return;
188
- const ie = e;
189
- const text = ie.data ?? '';
190
- _textData.isComposing = ie.isComposing;
191
- _textData.text = text;
192
- emitSignal(manager.onTextInput, _textData);
193
- };
194
- const onCompositionUpdate = (e) => {
195
- if (!manager.enabled)
196
- return;
197
- const ce = e;
198
- const text = ce.data ?? '';
199
- _textData.isComposing = true;
200
- _textData.text = text;
201
- emitSignal(manager.onTextEdit, _textData);
202
- };
203
- element.addEventListener('beforeinput', onBeforeInput);
204
- element.addEventListener('compositionupdate', onCompositionUpdate);
205
- setInputBinding(manager, element, kTextInput, () => {
206
- element.removeEventListener('beforeinput', onBeforeInput);
207
- element.removeEventListener('compositionupdate', onCompositionUpdate);
208
- });
209
- // Suppress unused-options warning; options accepted for API symmetry.
210
- void options;
66
+ export function attachTextInput(manager, source, options) {
67
+ const release = getInputIngressBackend().attachText(source, getInputIngressSink(manager), options);
68
+ setInputBinding(manager, source, kTextInput, release);
211
69
  }
212
- export function attachWheelInput(manager, element, options) {
213
- const preventDefault = options?.preventDefault ?? true;
214
- const handler = (e) => {
215
- if (!manager.enabled)
216
- return;
217
- const we = e;
218
- if (preventDefault)
219
- we.preventDefault();
220
- setInputPointerData(_pointerData, we, we.deltaX, we.deltaY);
221
- _pointerData.wheelMode = getMouseWheelModeFromDomWheelEvent(we);
222
- emitSignal(manager.onWheel, _pointerData);
223
- };
224
- element.addEventListener('wheel', handler, { passive: !preventDefault });
225
- setInputBinding(manager, element, kWheelInput, () => element.removeEventListener('wheel', handler));
70
+ export function attachWheelInput(manager, source, options) {
71
+ const release = getInputIngressBackend().attachWheel(source, getInputIngressSink(manager), options);
72
+ setInputBinding(manager, source, kWheelInput, release);
226
73
  }
227
74
  /**
228
75
  * Subscribes `state` to all signals on `manager` to maintain a live held-state snapshot.
@@ -268,10 +115,9 @@ export function connectInputStateToInputManager(state, manager) {
268
115
  const onPointerCancel = (data) => {
269
116
  state.pointerButtonsDown.delete(data.pointerId);
270
117
  };
271
- // Same transition rules as the keyboard pair above. pollGamepadInput already edge-detects before it
272
- // emits, so it cannot produce a repeat itself — but these signals are public and a native backend
273
- // reporting held buttons each poll is a supported source, so the guard belongs on the state machine
274
- // rather than in one producer.
118
+ // Same transition rules as the keyboard pair above. The Web adapter edge-detects before it emits,
119
+ // but these signals are public and a native backend reporting held buttons each poll is a supported
120
+ // source, so the guard belongs on the state machine rather than in one producer.
275
121
  const onGamepadButtonDown = (data) => {
276
122
  const key = data.gamepad * MAX_GAMEPAD_BUTTONS + data.button;
277
123
  if (!state.gamepadButtonsDown.has(key))
@@ -414,23 +260,261 @@ export function createInputState() {
414
260
  pointerButtonsDown: new Map(),
415
261
  };
416
262
  }
417
- export function detachGamepadInput(manager, target) {
418
- clearInputBinding(manager, target, kGamepadInput);
263
+ /** Explicit browser adapter for the process-wide input-ingress seam. */
264
+ export function createWebInputIngressBackend() {
265
+ return {
266
+ attachGamepad(source, sink) {
267
+ const target = getWebInputEventTarget(source);
268
+ if (target === null)
269
+ return noopInputIngressRelease;
270
+ const previousAxes = new Map();
271
+ const previousButtons = new Map();
272
+ let released = false;
273
+ let frameHandle = 0;
274
+ const onGamepadConnected = (event) => {
275
+ if (released || !sink.isEnabled())
276
+ return;
277
+ const gamepad = event.gamepad;
278
+ previousAxes.set(gamepad.index, Array.from(gamepad.axes));
279
+ previousButtons.set(gamepad.index, Array.from(gamepad.buttons, (button) => button.pressed));
280
+ setInputGamepadConnectData(_connectData, gamepad);
281
+ sink.gamepadConnect(_connectData);
282
+ };
283
+ const onGamepadDisconnected = (event) => {
284
+ if (released || !sink.isEnabled())
285
+ return;
286
+ const gamepad = event.gamepad;
287
+ previousAxes.delete(gamepad.index);
288
+ previousButtons.delete(gamepad.index);
289
+ setInputGamepadConnectData(_connectData, gamepad);
290
+ sink.gamepadDisconnect(_connectData);
291
+ };
292
+ const poll = () => {
293
+ if (!sink.isEnabled() || typeof navigator.getGamepads !== 'function')
294
+ return;
295
+ const now = performance.now();
296
+ for (const gamepad of navigator.getGamepads()) {
297
+ if (gamepad === null)
298
+ continue;
299
+ const axes = previousAxes.get(gamepad.index) ?? [];
300
+ const buttons = previousButtons.get(gamepad.index) ?? [];
301
+ for (let index = 0; index < gamepad.axes.length; index++) {
302
+ const value = gamepad.axes[index];
303
+ if (value === axes[index])
304
+ continue;
305
+ axes[index] = value;
306
+ _axisData.axis = index;
307
+ _axisData.gamepad = gamepad.index;
308
+ _axisData.timeStamp = now;
309
+ _axisData.value = value;
310
+ sink.gamepadAxisMove(_axisData);
311
+ if (released)
312
+ return;
313
+ }
314
+ for (let index = 0; index < gamepad.buttons.length; index++) {
315
+ const button = gamepad.buttons[index];
316
+ const wasPressed = buttons[index] ?? false;
317
+ if (button.pressed === wasPressed)
318
+ continue;
319
+ buttons[index] = button.pressed;
320
+ _buttonData.button = index;
321
+ _buttonData.gamepad = gamepad.index;
322
+ _buttonData.timeStamp = now;
323
+ _buttonData.value = button.value;
324
+ if (button.pressed)
325
+ sink.gamepadButtonDown(_buttonData);
326
+ else
327
+ sink.gamepadButtonUp(_buttonData);
328
+ if (released)
329
+ return;
330
+ }
331
+ previousAxes.set(gamepad.index, axes);
332
+ previousButtons.set(gamepad.index, buttons);
333
+ }
334
+ };
335
+ const loop = () => {
336
+ if (released)
337
+ return;
338
+ // Schedule first so a signal handler that detaches reentrantly cancels the only future frame.
339
+ frameHandle = requestAnimationFrame(loop);
340
+ poll();
341
+ };
342
+ target.addEventListener('gamepadconnected', onGamepadConnected);
343
+ target.addEventListener('gamepaddisconnected', onGamepadDisconnected);
344
+ frameHandle = requestAnimationFrame(loop);
345
+ return () => {
346
+ if (released)
347
+ return;
348
+ released = true;
349
+ target.removeEventListener('gamepadconnected', onGamepadConnected);
350
+ target.removeEventListener('gamepaddisconnected', onGamepadDisconnected);
351
+ cancelAnimationFrame(frameHandle);
352
+ };
353
+ },
354
+ attachKeyboard(source, sink, options) {
355
+ const target = getWebInputEventTarget(source);
356
+ if (target === null)
357
+ return noopInputIngressRelease;
358
+ const preventDefault = options?.preventDefault ?? true;
359
+ const onKeyDown = (event) => {
360
+ if (!sink.isEnabled())
361
+ return;
362
+ const keyboardEvent = event;
363
+ if (preventDefault)
364
+ keyboardEvent.preventDefault();
365
+ setInputKeyboardData(_keyboardData, keyboardEvent);
366
+ sink.keyDown(_keyboardData);
367
+ };
368
+ const onKeyUp = (event) => {
369
+ if (!sink.isEnabled())
370
+ return;
371
+ const keyboardEvent = event;
372
+ if (preventDefault)
373
+ keyboardEvent.preventDefault();
374
+ setInputKeyboardData(_keyboardData, keyboardEvent);
375
+ sink.keyUp(_keyboardData);
376
+ };
377
+ target.addEventListener('keydown', onKeyDown);
378
+ target.addEventListener('keyup', onKeyUp);
379
+ return () => {
380
+ target.removeEventListener('keydown', onKeyDown);
381
+ target.removeEventListener('keyup', onKeyUp);
382
+ };
383
+ },
384
+ attachPointer(source, sink, options) {
385
+ const target = getWebInputEventTarget(source);
386
+ if (target === null)
387
+ return noopInputIngressRelease;
388
+ const preventDefault = options?.preventDefault ?? true;
389
+ const onContextMenu = (event) => {
390
+ if (preventDefault)
391
+ event.preventDefault();
392
+ };
393
+ const onPointerCancel = (event) => {
394
+ if (!sink.isEnabled())
395
+ return;
396
+ if (preventDefault)
397
+ event.preventDefault();
398
+ setInputPointerData(_pointerData, event, 0, 0);
399
+ sink.pointerCancel(_pointerData);
400
+ };
401
+ const onPointerDown = (event) => {
402
+ if (!sink.isEnabled())
403
+ return;
404
+ if (preventDefault)
405
+ event.preventDefault();
406
+ setInputPointerData(_pointerData, event, 0, 0);
407
+ sink.pointerDown(_pointerData);
408
+ };
409
+ const onPointerMove = (event) => {
410
+ if (!sink.isEnabled())
411
+ return;
412
+ if (preventDefault)
413
+ event.preventDefault();
414
+ setInputPointerData(_pointerData, event, 0, 0);
415
+ sink.pointerMove(_pointerData);
416
+ };
417
+ const onPointerUp = (event) => {
418
+ if (!sink.isEnabled())
419
+ return;
420
+ if (preventDefault)
421
+ event.preventDefault();
422
+ setInputPointerData(_pointerData, event, 0, 0);
423
+ sink.pointerUp(_pointerData);
424
+ };
425
+ target.addEventListener('contextmenu', onContextMenu);
426
+ target.addEventListener('pointercancel', onPointerCancel);
427
+ target.addEventListener('pointerdown', onPointerDown);
428
+ target.addEventListener('pointermove', onPointerMove);
429
+ target.addEventListener('pointerup', onPointerUp);
430
+ return () => {
431
+ target.removeEventListener('contextmenu', onContextMenu);
432
+ target.removeEventListener('pointercancel', onPointerCancel);
433
+ target.removeEventListener('pointerdown', onPointerDown);
434
+ target.removeEventListener('pointermove', onPointerMove);
435
+ target.removeEventListener('pointerup', onPointerUp);
436
+ };
437
+ },
438
+ attachRelativePointer(source, sink, options) {
439
+ const target = getWebInputOwnerDocumentTarget(source);
440
+ if (target === null)
441
+ return noopInputIngressRelease;
442
+ const preventDefault = options?.preventDefault ?? true;
443
+ const onMouseMove = (event) => {
444
+ if (!sink.isEnabled())
445
+ return;
446
+ const mouseEvent = event;
447
+ if (preventDefault)
448
+ mouseEvent.preventDefault();
449
+ setInputPointerData(_pointerData, mouseEvent, mouseEvent.movementX, mouseEvent.movementY);
450
+ sink.pointerMoveRelative(_pointerData);
451
+ };
452
+ target.addEventListener('mousemove', onMouseMove);
453
+ return () => target.removeEventListener('mousemove', onMouseMove);
454
+ },
455
+ attachText(source, sink) {
456
+ const target = getWebInputEventTarget(source);
457
+ if (target === null)
458
+ return noopInputIngressRelease;
459
+ const onBeforeInput = (event) => {
460
+ if (!sink.isEnabled())
461
+ return;
462
+ const inputEvent = event;
463
+ _textData.isComposing = inputEvent.isComposing;
464
+ _textData.text = inputEvent.data ?? '';
465
+ sink.textInput(_textData);
466
+ };
467
+ const onCompositionUpdate = (event) => {
468
+ if (!sink.isEnabled())
469
+ return;
470
+ _textData.isComposing = true;
471
+ _textData.text = event.data ?? '';
472
+ sink.textEdit(_textData);
473
+ };
474
+ target.addEventListener('beforeinput', onBeforeInput);
475
+ target.addEventListener('compositionupdate', onCompositionUpdate);
476
+ return () => {
477
+ target.removeEventListener('beforeinput', onBeforeInput);
478
+ target.removeEventListener('compositionupdate', onCompositionUpdate);
479
+ };
480
+ },
481
+ attachWheel(source, sink, options) {
482
+ const target = getWebInputEventTarget(source);
483
+ if (target === null)
484
+ return noopInputIngressRelease;
485
+ const preventDefault = options?.preventDefault ?? true;
486
+ const onWheel = (event) => {
487
+ if (!sink.isEnabled())
488
+ return;
489
+ const wheelEvent = event;
490
+ if (preventDefault)
491
+ wheelEvent.preventDefault();
492
+ setInputPointerData(_pointerData, wheelEvent, wheelEvent.deltaX, wheelEvent.deltaY);
493
+ _pointerData.wheelMode = getMouseWheelModeFromDomWheelEvent(wheelEvent);
494
+ sink.wheel(_pointerData);
495
+ };
496
+ target.addEventListener('wheel', onWheel, { passive: !preventDefault });
497
+ return () => target.removeEventListener('wheel', onWheel);
498
+ },
499
+ };
500
+ }
501
+ export function detachGamepadInput(manager, source) {
502
+ clearInputBinding(manager, source, kGamepadInput);
419
503
  }
420
- export function detachKeyboardInput(manager, target) {
421
- clearInputBinding(manager, target, kKeyboardInput);
504
+ export function detachKeyboardInput(manager, source) {
505
+ clearInputBinding(manager, source, kKeyboardInput);
422
506
  }
423
- export function detachPointerInput(manager, element) {
424
- clearInputBinding(manager, element, kPointerInput);
507
+ export function detachPointerInput(manager, source) {
508
+ clearInputBinding(manager, source, kPointerInput);
425
509
  }
426
- export function detachRelativePointerInput(manager, element) {
427
- clearInputBinding(manager, element, kRelativePointerInput);
510
+ export function detachRelativePointerInput(manager, source) {
511
+ clearInputBinding(manager, source, kRelativePointerInput);
428
512
  }
429
- export function detachTextInput(manager, element) {
430
- clearInputBinding(manager, element, kTextInput);
513
+ export function detachTextInput(manager, source) {
514
+ clearInputBinding(manager, source, kTextInput);
431
515
  }
432
- export function detachWheelInput(manager, element) {
433
- clearInputBinding(manager, element, kWheelInput);
516
+ export function detachWheelInput(manager, source) {
517
+ clearInputBinding(manager, source, kWheelInput);
434
518
  }
435
519
  /**
436
520
  * Rolls the per-frame edge sets on `state`, clearing `justPressedKeys`,
@@ -444,15 +528,6 @@ export function endInputStateFrame(state) {
444
528
  state.justPressedGamepadButtons.clear();
445
529
  state.justReleasedGamepadButtons.clear();
446
530
  }
447
- /**
448
- * Exits pointer lock, returning the pointer to normal movement.
449
- * No-op if pointer lock is not currently active.
450
- */
451
- export function exitInputPointerLock() {
452
- if (document.exitPointerLock) {
453
- document.exitPointerLock();
454
- }
455
- }
456
531
  /**
457
532
  * Returns coalesced pointer event data for a `pointermove` event, iterating
458
533
  * over the high-frequency intermediate positions captured since the last
@@ -502,6 +577,9 @@ export function getGamepadButtonName(mapping, index) {
502
577
  export function getInputGamepadAxis(state, gamepad, axis) {
503
578
  return state.axisValues.get(gamepad * MAX_GAMEPAD_AXES + axis) ?? 0;
504
579
  }
580
+ export function getInputIngressBackend() {
581
+ return _customInputIngressBackend ?? _hostInputIngressBackend ?? _webInputIngressBackend;
582
+ }
505
583
  export function getKeyCodeFromDomKeyboardEvent(event) {
506
584
  const code = getKeyCodeFromDomKeyboardCode(event.code, event.location);
507
585
  if (code !== KeyCode.UNKNOWN)
@@ -538,11 +616,10 @@ export function getMouseWheelModeFromDomWheelEvent(event) {
538
616
  return 'pages';
539
617
  return 'unknown';
540
618
  }
541
- /**
542
- * Returns `true` if pointer lock is currently active on any element.
543
- */
544
- export function hasInputPointerLock() {
545
- return document.pointerLockElement !== null;
619
+ // First host wins; a custom backend installed through setInputIngressBackend always takes precedence.
620
+ export function installInputIngressHostBackend(backend) {
621
+ if (_hostInputIngressBackend === null)
622
+ _hostInputIngressBackend = backend;
546
623
  }
547
624
  /**
548
625
  * Returns `true` if the given gamepad button is currently held.
@@ -564,49 +641,6 @@ export function isInputKeyDown(state, keyCode) {
564
641
  export function isInputPointerButtonDown(state, pointerId, button) {
565
642
  return ((state.pointerButtonsDown.get(pointerId) ?? 0) & (1 << button)) !== 0;
566
643
  }
567
- export function pollGamepadInput(manager) {
568
- if (!manager.enabled || typeof navigator.getGamepads !== 'function')
569
- return;
570
- const now = performance.now();
571
- const prev = getOrCreateGamepadPollState(manager);
572
- const gamepads = navigator.getGamepads();
573
- for (const pad of gamepads) {
574
- if (pad === null)
575
- continue;
576
- const prevAxes = prev.axes.get(pad.index) ?? [];
577
- const prevButtons = prev.buttons.get(pad.index) ?? [];
578
- for (let i = 0; i < pad.axes.length; i++) {
579
- const value = pad.axes[i];
580
- if (value !== prevAxes[i]) {
581
- prevAxes[i] = value;
582
- _axisData.axis = i;
583
- _axisData.gamepad = pad.index;
584
- _axisData.timeStamp = now;
585
- _axisData.value = value;
586
- emitSignal(manager.onGamepadAxisMove, _axisData);
587
- }
588
- }
589
- for (let i = 0; i < pad.buttons.length; i++) {
590
- const btn = pad.buttons[i];
591
- const wasPressed = prevButtons[i] ?? false;
592
- if (btn.pressed !== wasPressed) {
593
- prevButtons[i] = btn.pressed;
594
- _buttonData.button = i;
595
- _buttonData.gamepad = pad.index;
596
- _buttonData.timeStamp = now;
597
- _buttonData.value = btn.value;
598
- if (btn.pressed) {
599
- emitSignal(manager.onGamepadButtonDown, _buttonData);
600
- }
601
- else {
602
- emitSignal(manager.onGamepadButtonUp, _buttonData);
603
- }
604
- }
605
- }
606
- prev.axes.set(pad.index, prevAxes);
607
- prev.buttons.set(pad.index, prevButtons);
608
- }
609
- }
610
644
  /**
611
645
  * Releases pointer capture for `pointerId` from `element`, allowing pointer
612
646
  * events to fire on the element under the pointer again.
@@ -620,22 +654,12 @@ export function releaseInputPointerCapture(element, pointerId) {
620
654
  // Ignore — the pointer may have already been released.
621
655
  }
622
656
  }
623
- /**
624
- * Requests pointer lock on `element`. Returns a `Promise<boolean>` that
625
- * resolves to `true` when lock is granted or `false` if the request is
626
- * rejected (e.g. outside a user gesture, or the browser denies it).
627
- */
628
- export function requestInputPointerLock(element) {
629
- try {
630
- const result = element.requestPointerLock();
631
- if (result instanceof Promise) {
632
- return result.then(() => true, () => false);
633
- }
634
- return Promise.resolve(true);
635
- }
636
- catch {
637
- return Promise.resolve(false);
638
- }
657
+ export function resetInputIngressBackendForTest() {
658
+ _customInputIngressBackend = null;
659
+ _hostInputIngressBackend = null;
660
+ }
661
+ export function setInputIngressBackend(backend) {
662
+ _customInputIngressBackend = backend;
639
663
  }
640
664
  /**
641
665
  * Explicitly captures all pointer events for `pointerId` to `element`,
@@ -995,22 +1019,107 @@ const _textData = {
995
1019
  isComposing: false,
996
1020
  text: '',
997
1021
  };
998
- const _gamepadPollStates = new WeakMap();
999
- function getOrCreateGamepadPollState(manager) {
1000
- let state = _gamepadPollStates.get(manager);
1001
- if (state === undefined) {
1002
- state = { axes: new Map(), buttons: new Map() };
1003
- _gamepadPollStates.set(manager, state);
1004
- }
1005
- return state;
1006
- }
1007
1022
  const _axisData = { axis: 0, gamepad: 0, timeStamp: 0, value: 0 };
1008
1023
  const _buttonData = { button: 0, gamepad: 0, timeStamp: 0, value: 0 };
1009
1024
  const _connectData = { gamepad: 0, id: '', mapping: '' };
1010
- // Internal teardown registry: maps a manager to its per-target, per-input-kind cleanup closures.
1011
- // Kept off the public InputManager entity (a side table like `_gamepadPollStates`) so attach/detach
1012
- // track bindings internally and callers hold nothing. The nested EventTarget key lets one manager
1013
- // attach the same input kind to multiple elements and detach each precisely.
1025
+ const _inputIngressSinks = new WeakMap();
1026
+ function getInputIngressSink(manager) {
1027
+ let sink = _inputIngressSinks.get(manager);
1028
+ if (sink !== undefined)
1029
+ return sink;
1030
+ sink = {
1031
+ gamepadAxisMove(data) {
1032
+ if (manager.enabled)
1033
+ emitSignal(manager.onGamepadAxisMove, data);
1034
+ },
1035
+ gamepadButtonDown(data) {
1036
+ if (manager.enabled)
1037
+ emitSignal(manager.onGamepadButtonDown, data);
1038
+ },
1039
+ gamepadButtonUp(data) {
1040
+ if (manager.enabled)
1041
+ emitSignal(manager.onGamepadButtonUp, data);
1042
+ },
1043
+ gamepadConnect(data) {
1044
+ if (manager.enabled)
1045
+ emitSignal(manager.onGamepadConnect, data);
1046
+ },
1047
+ gamepadDisconnect(data) {
1048
+ if (manager.enabled)
1049
+ emitSignal(manager.onGamepadDisconnect, data);
1050
+ },
1051
+ isEnabled() {
1052
+ return manager.enabled;
1053
+ },
1054
+ keyDown(data) {
1055
+ if (manager.enabled)
1056
+ emitSignal(manager.onKeyDown, data);
1057
+ },
1058
+ keyUp(data) {
1059
+ if (manager.enabled)
1060
+ emitSignal(manager.onKeyUp, data);
1061
+ },
1062
+ pointerCancel(data) {
1063
+ if (manager.enabled)
1064
+ emitSignal(manager.onPointerCancel, data);
1065
+ },
1066
+ pointerDown(data) {
1067
+ if (manager.enabled)
1068
+ emitSignal(manager.onPointerDown, data);
1069
+ },
1070
+ pointerMove(data) {
1071
+ if (manager.enabled)
1072
+ emitSignal(manager.onPointerMove, data);
1073
+ },
1074
+ pointerMoveRelative(data) {
1075
+ if (manager.enabled)
1076
+ emitSignal(manager.onPointerMoveRelative, data);
1077
+ },
1078
+ pointerUp(data) {
1079
+ if (manager.enabled)
1080
+ emitSignal(manager.onPointerUp, data);
1081
+ },
1082
+ textEdit(data) {
1083
+ if (manager.enabled)
1084
+ emitSignal(manager.onTextEdit, data);
1085
+ },
1086
+ textInput(data) {
1087
+ if (manager.enabled)
1088
+ emitSignal(manager.onTextInput, data);
1089
+ },
1090
+ wheel(data) {
1091
+ if (manager.enabled)
1092
+ emitSignal(manager.onWheel, data);
1093
+ },
1094
+ };
1095
+ _inputIngressSinks.set(manager, sink);
1096
+ return sink;
1097
+ }
1098
+ function getWebInputEventTarget(source) {
1099
+ const candidate = source;
1100
+ return typeof candidate.addEventListener === 'function' && typeof candidate.removeEventListener === 'function'
1101
+ ? candidate
1102
+ : null;
1103
+ }
1104
+ function getWebInputOwnerDocumentTarget(source) {
1105
+ if (!('ownerDocument' in source))
1106
+ return null;
1107
+ const ownerDocument = source.ownerDocument;
1108
+ return ownerDocument === undefined || ownerDocument === null ? null : getWebInputEventTarget(ownerDocument);
1109
+ }
1110
+ function noopInputIngressRelease() { }
1111
+ function setInputGamepadConnectData(out, gamepad) {
1112
+ out.gamepad = gamepad.index;
1113
+ out.id = gamepad.id;
1114
+ out.mapping = gamepad.mapping === 'standard' ? 'standard' : gamepad.mapping === '' ? '' : 'raw';
1115
+ }
1116
+ const _webInputIngressBackend = createWebInputIngressBackend();
1117
+ let _customInputIngressBackend = null;
1118
+ let _hostInputIngressBackend = null;
1119
+ // Internal teardown registry: maps a manager to its per-source, per-input-kind origin release.
1120
+ // Kept off the public InputManager entity so attach/detach track bindings internally and callers hold
1121
+ // nothing. The exact source identity lets one manager attach the same input kind to multiple
1122
+ // windows/sources and detach each precisely.
1014
1123
  const kGamepadInput = Symbol();
1015
1124
  const kKeyboardInput = Symbol();
1016
1125
  const kPointerInput = Symbol();
@@ -1018,26 +1127,33 @@ const kRelativePointerInput = Symbol();
1018
1127
  const kTextInput = Symbol();
1019
1128
  const kWheelInput = Symbol();
1020
1129
  const _inputBindings = new WeakMap();
1021
- function clearInputBinding(manager, target, kind) {
1022
- const byKind = _inputBindings.get(manager)?.get(target);
1023
- const cleanup = byKind?.get(kind);
1024
- if (cleanup === undefined)
1130
+ function clearInputBinding(manager, source, kind) {
1131
+ const bySource = _inputBindings.get(manager);
1132
+ const byKind = bySource?.get(source);
1133
+ const release = byKind?.get(kind);
1134
+ if (release === undefined)
1025
1135
  return;
1026
- cleanup();
1027
1136
  byKind.delete(kind);
1137
+ if (byKind.size === 0)
1138
+ bySource.delete(source);
1139
+ release();
1028
1140
  }
1029
- function setInputBinding(manager, target, kind, cleanup) {
1030
- let byTarget = _inputBindings.get(manager);
1031
- if (byTarget === undefined) {
1032
- byTarget = new Map();
1033
- _inputBindings.set(manager, byTarget);
1141
+ function setInputBinding(manager, source, kind, release) {
1142
+ let bySource = _inputBindings.get(manager);
1143
+ if (bySource === undefined) {
1144
+ bySource = new Map();
1145
+ _inputBindings.set(manager, bySource);
1034
1146
  }
1035
- let byKind = byTarget.get(target);
1147
+ let byKind = bySource.get(source);
1036
1148
  if (byKind === undefined) {
1037
1149
  byKind = new Map();
1038
- byTarget.set(target, byKind);
1150
+ bySource.set(source, byKind);
1151
+ }
1152
+ const previous = byKind.get(kind);
1153
+ if (previous !== undefined) {
1154
+ byKind.delete(kind);
1155
+ previous();
1039
1156
  }
1040
- byKind.get(kind)?.();
1041
- byKind.set(kind, cleanup);
1157
+ byKind.set(kind, release);
1042
1158
  }
1043
1159
  //# sourceMappingURL=inputManager.js.map