@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.
- package/README.md +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inputManager.d.ts +19 -29
- package/dist/inputManager.d.ts.map +1 -1
- package/dist/inputManager.js +403 -287
- package/dist/inputManager.js.map +1 -1
- package/package.json +3 -3
- package/src/index.test.ts +27 -0
- package/src/inputManager.test.ts +414 -165
package/dist/inputManager.js
CHANGED
|
@@ -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,
|
|
51
|
-
const
|
|
52
|
-
|
|
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,
|
|
92
|
-
const
|
|
93
|
-
|
|
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,
|
|
119
|
-
const
|
|
120
|
-
|
|
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,
|
|
170
|
-
const
|
|
171
|
-
|
|
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,
|
|
185
|
-
const
|
|
186
|
-
|
|
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,
|
|
213
|
-
const
|
|
214
|
-
|
|
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.
|
|
272
|
-
//
|
|
273
|
-
//
|
|
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
|
-
|
|
418
|
-
|
|
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,
|
|
421
|
-
clearInputBinding(manager,
|
|
504
|
+
export function detachKeyboardInput(manager, source) {
|
|
505
|
+
clearInputBinding(manager, source, kKeyboardInput);
|
|
422
506
|
}
|
|
423
|
-
export function detachPointerInput(manager,
|
|
424
|
-
clearInputBinding(manager,
|
|
507
|
+
export function detachPointerInput(manager, source) {
|
|
508
|
+
clearInputBinding(manager, source, kPointerInput);
|
|
425
509
|
}
|
|
426
|
-
export function detachRelativePointerInput(manager,
|
|
427
|
-
clearInputBinding(manager,
|
|
510
|
+
export function detachRelativePointerInput(manager, source) {
|
|
511
|
+
clearInputBinding(manager, source, kRelativePointerInput);
|
|
428
512
|
}
|
|
429
|
-
export function detachTextInput(manager,
|
|
430
|
-
clearInputBinding(manager,
|
|
513
|
+
export function detachTextInput(manager, source) {
|
|
514
|
+
clearInputBinding(manager, source, kTextInput);
|
|
431
515
|
}
|
|
432
|
-
export function detachWheelInput(manager,
|
|
433
|
-
clearInputBinding(manager,
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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,
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
|
|
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,
|
|
1030
|
-
let
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1033
|
-
_inputBindings.set(manager,
|
|
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 =
|
|
1147
|
+
let byKind = bySource.get(source);
|
|
1036
1148
|
if (byKind === undefined) {
|
|
1037
1149
|
byKind = new Map();
|
|
1038
|
-
|
|
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.
|
|
1041
|
-
byKind.set(kind, cleanup);
|
|
1157
|
+
byKind.set(kind, release);
|
|
1042
1158
|
}
|
|
1043
1159
|
//# sourceMappingURL=inputManager.js.map
|