@hology/core 0.0.221 → 0.0.222

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.
@@ -19,7 +19,14 @@ export declare class InputService {
19
19
  private lastActiveDevice;
20
20
  readonly onDeviceChanged: Subject<InputDevice>;
21
21
  private prevGamepadButtonStates;
22
+ private suppressedGamepadButtons;
22
23
  getLastActiveDevice(): InputDevice;
24
+ /**
25
+ * Ignores button transitions for any gamepad buttons currently held down
26
+ * until each button is released. This is useful when dismissing modal UI
27
+ * whose confirm button is also bound to a gameplay action.
28
+ */
29
+ suppressHeldGamepadButtonsUntilRelease(): void;
23
30
  private setLastActiveDevice;
24
31
  private onKeyDown;
25
32
  /**
@@ -1,4 +1,4 @@
1
- import{__decorate as e}from"tslib";import{Service as t}from"typedi";import{inject as s}from"../inject.js";import{ViewController as i}from"../services/render.js";import{MathUtils as n}from"three";import{Subject as o}from"rxjs";let a=class{constructor(){this.view=s(i),this.inputs=new Map,this.deltaCallbacks=new Map,this.toggleCallbacks=new Map,this.keybinds=new Map,this.mousebinds=new Map,this.touchmoveBinds=new Map,this.wheelbinds=new Map,this.gamepadButtonBinds=new Map,this.gamepadAxisBinds=new Map,this.lastActiveDevice="kbm",this.onDeviceChanged=new o,this.prevGamepadButtonStates=new Map,this.onKeyDown=e=>{this.setLastActiveDevice("kbm"),this.view.paused||this.keybinds.forEach((t,s)=>{for(const i of t)if(i.test(e))if(this.inputs.has(s))this.inputs.get(s)(!0);else if(this.toggleCallbacks.has(s)){this.toggleCallbacks.get(s)(!0);this.activeToggleCallbacks.findIndex(e=>e.keybind===i&&e.toggle===this.toggleCallbacks.get(s))<0&&this.activeToggleCallbacks.push({keybind:i,toggle:this.toggleCallbacks.get(s)})}else console.warn("No input callback for registered for key bind "+i.display())})},this.activeToggleCallbacks=[],this.onKeyUp=e=>{this.setLastActiveDevice("kbm"),this.keybinds.forEach((t,s)=>{for(const i of t)if(i.test(e))if(this.inputs.has(s))this.inputs.get(s)(!1);else if(this.toggleCallbacks.has(s)){this.toggleCallbacks.get(s)(!1);const e=this.activeToggleCallbacks.findIndex(e=>e.keybind===i&&e.toggle===this.toggleCallbacks.get(s));e>=0&&this.activeToggleCallbacks.splice(e,1);for(const e of this.activeToggleCallbacks)e.toggle(!0)}else console.warn("No input callback for registered for key bind "+i.display())})},this.onMouseMove=e=>{if((Math.abs(e.movementX)>1||Math.abs(e.movementY)>1)&&this.setLastActiveDevice("kbm"),this.view.paused)return;if(!(null!=document.pointerLockElement||null!=document.mozPointerLockElement))return;const t=n.clamp(e.movementX,-50,50),s=n.clamp(e.movementY,-50,50);Math.abs(t)>200||Math.abs(s)>200||this.mousebinds.forEach((e,i)=>{const n=this.inputs.get(i)??this.deltaCallbacks.get(i);if(null!=n)switch(e.axis){case"x":n(t*e.multiplier);break;case"y":n(s*e.multiplier)}else console.warn("No input callback for registered for mouse bind "+e.axis)})},this.onTouchStart=e=>{if(this.setLastActiveDevice("kbm"),!this.view.paused){for(let t=0;t<=e.touches.length;t++){const s=e.touches[t];if(s?.target instanceof HTMLCanvasElement)return void(this.prevTouchEvent=s)}e.preventDefault()}},this.onTouchMove=e=>{if(this.view.paused)return;if(null==this.prevTouchEvent)return;let t=null;for(let s=0;s<=e.touches.length;s++){const i=e.touches[s];if(i&&i.target instanceof HTMLCanvasElement&&i.identifier===this.prevTouchEvent.identifier){t=i;break}}null!=t&&(this.touchmoveBinds.forEach((e,s)=>{const i=this.inputs.get(s)??this.deltaCallbacks.get(s);if(null!=i)switch(e.axis){case"x":i((t.clientX-this.prevTouchEvent.clientX)*e.multiplier);break;case"y":i((t.clientY-this.prevTouchEvent.clientY)*e.multiplier)}else console.warn("No input callback for registered for mouse bind "+e.axis)}),this.prevTouchEvent=t,e.preventDefault())},this.onTouchEnd=e=>{this.prevTouchEvent=null,e.preventDefault()},this.onWheel=e=>{this.setLastActiveDevice("kbm"),this.view.paused||this.wheelbinds.forEach((t,s)=>{const i=this.inputs.get(s)??this.deltaCallbacks.get(s);null!=i?i(e.deltaY*t.multiplier):console.warn("No input callback for registered for wheel bind")})}}getLastActiveDevice(){return this.lastActiveDevice}setLastActiveDevice(e){this.lastActiveDevice!==e&&(this.lastActiveDevice=e,this.onDeviceChanged.next(e))}start(){document.addEventListener("keydown",this.onKeyDown),document.addEventListener("mousedown",this.onKeyDown),document.addEventListener("mouseup",this.onKeyUp),document.addEventListener("keyup",this.onKeyUp),document.addEventListener("mousemove",this.onMouseMove),this.view.htmlElement.addEventListener("touchstart",this.onTouchStart,{passive:!1}),this.view.htmlElement.addEventListener("touchmove",this.onTouchMove,{passive:!1}),this.view.htmlElement.addEventListener("touchend",this.onTouchEnd,{passive:!1}),document.addEventListener("wheel",this.onWheel)}stop(){document.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("mousedown",this.onKeyDown),document.removeEventListener("mouseup",this.onKeyUp),document.removeEventListener("keyup",this.onKeyUp),document.removeEventListener("mousemove",this.onMouseMove),this.view.htmlElement.removeEventListener("touchstart",this.onTouchStart),this.view.htmlElement.removeEventListener("touchmove",this.onTouchMove),this.view.htmlElement.removeEventListener("touchend",this.onTouchEnd),document.removeEventListener("wheel",this.onWheel)}update(e){if(this.view.paused)return;const t=navigator.getGamepads();for(let e=0;e<t.length;e++){const s=t[e];if(!s||"standard"!==s.mapping)continue;let i=this.prevGamepadButtonStates.get(e);null==i&&(i=new Array(s.buttons.length).fill(!1),this.prevGamepadButtonStates.set(e,i)),this.gamepadButtonBinds.forEach((e,t)=>{for(const n of e){const e=n.button,o=s.buttons[e];if(!o)continue;const a=o.pressed,h=o.value,l=i[e];if(a!==l){this.setLastActiveDevice("gamepad");const e=this.toggleCallbacks.get(t);e&&e(a)}const c=this.inputs.get(t)??this.deltaCallbacks.get(t);c&&(h>0||a||l&&0===h)&&"gamepad"===this.lastActiveDevice&&c(h)}});for(let e=0;e<s.buttons.length;e++)i[e]=s.buttons[e].pressed;this.gamepadAxisBinds.forEach((e,t)=>{for(const i of e){let e=s.axes[i.axis];if(e=Math.abs(e)<i.deadzone?0:(e-Math.sign(e)*i.deadzone)/(1-i.deadzone),0!==e&&this.setLastActiveDevice("gamepad"),"gamepad"!==this.lastActiveDevice)break;e*=i.multiplier;const n=this.inputs.get(t)??this.deltaCallbacks.get(t);n&&n(e)}})}}bind(e,t){this.inputs.set(e,t)}bindToggle(e,t){this.toggleCallbacks.set(e,t)}bindDelta(e,t){this.deltaCallbacks.set(e,t)}unbind(e){this.inputs.delete(e),this.toggleCallbacks.delete(e),this.deltaCallbacks.delete(e)}setKeybind(e,t){this.keybinds.has(e)||this.keybinds.set(e,[]),this.keybinds.get(e).push(t)}setMousebind(e,t){this.mousebinds.set(e,t)}setTouchMoveBind(e,t){this.touchmoveBinds.set(e,t)}setWheelbind(e,t){this.wheelbinds.set(e,t)}setGamepadButtonBind(e,t){this.gamepadButtonBinds.has(e)||this.gamepadButtonBinds.set(e,[]),this.gamepadButtonBinds.get(e).push(t)}setGamepadAxisBind(e,t){this.gamepadAxisBinds.has(e)||this.gamepadAxisBinds.set(e,[]),this.gamepadAxisBinds.get(e).push(t)}removeKeybind(e){this.keybinds.delete(e)}removeMousebind(e){this.mousebinds.delete(e)}removeWheelbind(e){this.wheelbinds.delete(e)}removeGamepadButtonBind(e){this.gamepadButtonBinds.delete(e)}removeGamepadAxisBind(e){this.gamepadAxisBinds.delete(e)}vibrate(e,t=1,s=1){const i=navigator.getGamepads();for(const n of i)n&&n.vibrationActuator&&n.vibrationActuator.playEffect("dual-rumble",{startDelay:0,duration:e,weakMagnitude:s,strongMagnitude:t})}};a=e([t()],a);export{a as InputService};/*
1
+ import{__decorate as e}from"tslib";import{Service as t}from"typedi";import{inject as s}from"../inject.js";import{ViewController as i}from"../services/render.js";import{MathUtils as n}from"three";import{Subject as o}from"rxjs";let a=class{constructor(){this.view=s(i),this.inputs=new Map,this.deltaCallbacks=new Map,this.toggleCallbacks=new Map,this.keybinds=new Map,this.mousebinds=new Map,this.touchmoveBinds=new Map,this.wheelbinds=new Map,this.gamepadButtonBinds=new Map,this.gamepadAxisBinds=new Map,this.lastActiveDevice="kbm",this.onDeviceChanged=new o,this.prevGamepadButtonStates=new Map,this.suppressedGamepadButtons=new Map,this.onKeyDown=e=>{this.setLastActiveDevice("kbm"),this.view.paused||this.keybinds.forEach((t,s)=>{for(const i of t)if(i.test(e))if(this.inputs.has(s))this.inputs.get(s)(!0);else if(this.toggleCallbacks.has(s)){this.toggleCallbacks.get(s)(!0);this.activeToggleCallbacks.findIndex(e=>e.keybind===i&&e.toggle===this.toggleCallbacks.get(s))<0&&this.activeToggleCallbacks.push({keybind:i,toggle:this.toggleCallbacks.get(s)})}else console.warn("No input callback for registered for key bind "+i.display())})},this.activeToggleCallbacks=[],this.onKeyUp=e=>{this.setLastActiveDevice("kbm"),this.keybinds.forEach((t,s)=>{for(const i of t)if(i.test(e))if(this.inputs.has(s))this.inputs.get(s)(!1);else if(this.toggleCallbacks.has(s)){this.toggleCallbacks.get(s)(!1);const e=this.activeToggleCallbacks.findIndex(e=>e.keybind===i&&e.toggle===this.toggleCallbacks.get(s));e>=0&&this.activeToggleCallbacks.splice(e,1);for(const e of this.activeToggleCallbacks)e.toggle(!0)}else console.warn("No input callback for registered for key bind "+i.display())})},this.onMouseMove=e=>{if((Math.abs(e.movementX)>1||Math.abs(e.movementY)>1)&&this.setLastActiveDevice("kbm"),this.view.paused)return;if(!(null!=document.pointerLockElement||null!=document.mozPointerLockElement))return;const t=n.clamp(e.movementX,-50,50),s=n.clamp(e.movementY,-50,50);Math.abs(t)>200||Math.abs(s)>200||this.mousebinds.forEach((e,i)=>{const n=this.inputs.get(i)??this.deltaCallbacks.get(i);if(null!=n)switch(e.axis){case"x":n(t*e.multiplier);break;case"y":n(s*e.multiplier)}else console.warn("No input callback for registered for mouse bind "+e.axis)})},this.onTouchStart=e=>{if(this.setLastActiveDevice("kbm"),!this.view.paused){for(let t=0;t<=e.touches.length;t++){const s=e.touches[t];if(s?.target instanceof HTMLCanvasElement)return void(this.prevTouchEvent=s)}e.preventDefault()}},this.onTouchMove=e=>{if(this.view.paused)return;if(null==this.prevTouchEvent)return;let t=null;for(let s=0;s<=e.touches.length;s++){const i=e.touches[s];if(i&&i.target instanceof HTMLCanvasElement&&i.identifier===this.prevTouchEvent.identifier){t=i;break}}null!=t&&(this.touchmoveBinds.forEach((e,s)=>{const i=this.inputs.get(s)??this.deltaCallbacks.get(s);if(null!=i)switch(e.axis){case"x":i((t.clientX-this.prevTouchEvent.clientX)*e.multiplier);break;case"y":i((t.clientY-this.prevTouchEvent.clientY)*e.multiplier)}else console.warn("No input callback for registered for mouse bind "+e.axis)}),this.prevTouchEvent=t,e.preventDefault())},this.onTouchEnd=e=>{this.prevTouchEvent=null,e.preventDefault()},this.onWheel=e=>{this.setLastActiveDevice("kbm"),this.view.paused||this.wheelbinds.forEach((t,s)=>{const i=this.inputs.get(s)??this.deltaCallbacks.get(s);null!=i?i(e.deltaY*t.multiplier):console.warn("No input callback for registered for wheel bind")})}}getLastActiveDevice(){return this.lastActiveDevice}suppressHeldGamepadButtonsUntilRelease(){const e=navigator.getGamepads();for(let t=0;t<e.length;t++){const s=e[t];if(!s||"standard"!==s.mapping)continue;let i=this.suppressedGamepadButtons.get(t);null==i&&(i=new Array(s.buttons.length).fill(!1),this.suppressedGamepadButtons.set(t,i));for(let e=0;e<s.buttons.length;e++)s.buttons[e].pressed&&(i[e]=!0)}}setLastActiveDevice(e){this.lastActiveDevice!==e&&(this.lastActiveDevice=e,this.onDeviceChanged.next(e))}start(){document.addEventListener("keydown",this.onKeyDown),document.addEventListener("mousedown",this.onKeyDown),document.addEventListener("mouseup",this.onKeyUp),document.addEventListener("keyup",this.onKeyUp),document.addEventListener("mousemove",this.onMouseMove),this.view.htmlElement.addEventListener("touchstart",this.onTouchStart,{passive:!1}),this.view.htmlElement.addEventListener("touchmove",this.onTouchMove,{passive:!1}),this.view.htmlElement.addEventListener("touchend",this.onTouchEnd,{passive:!1}),document.addEventListener("wheel",this.onWheel)}stop(){document.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("mousedown",this.onKeyDown),document.removeEventListener("mouseup",this.onKeyUp),document.removeEventListener("keyup",this.onKeyUp),document.removeEventListener("mousemove",this.onMouseMove),this.view.htmlElement.removeEventListener("touchstart",this.onTouchStart),this.view.htmlElement.removeEventListener("touchmove",this.onTouchMove),this.view.htmlElement.removeEventListener("touchend",this.onTouchEnd),document.removeEventListener("wheel",this.onWheel)}update(e){if(this.view.paused)return;const t=navigator.getGamepads();for(let e=0;e<t.length;e++){const s=t[e];if(!s||"standard"!==s.mapping)continue;let i=this.prevGamepadButtonStates.get(e);null==i&&(i=new Array(s.buttons.length).fill(!1),this.prevGamepadButtonStates.set(e,i));let n=this.suppressedGamepadButtons.get(e);null==n&&(n=new Array(s.buttons.length).fill(!1),this.suppressedGamepadButtons.set(e,n)),this.gamepadButtonBinds.forEach((e,t)=>{for(const o of e){const e=o.button,a=s.buttons[e];if(!a)continue;const l=a.pressed,h=a.value,d=i[e];if(n[e]){l||(n[e]=!1);continue}if(l!==d){this.setLastActiveDevice("gamepad");const e=this.toggleCallbacks.get(t);e&&e(l)}const c=this.inputs.get(t)??this.deltaCallbacks.get(t);c&&(h>0||l||d&&0===h)&&"gamepad"===this.lastActiveDevice&&c(h)}});for(let e=0;e<s.buttons.length;e++)i[e]=s.buttons[e].pressed;this.gamepadAxisBinds.forEach((e,t)=>{for(const i of e){let e=s.axes[i.axis];if(e=Math.abs(e)<i.deadzone?0:(e-Math.sign(e)*i.deadzone)/(1-i.deadzone),0!==e&&this.setLastActiveDevice("gamepad"),"gamepad"!==this.lastActiveDevice)break;e*=i.multiplier;const n=this.inputs.get(t)??this.deltaCallbacks.get(t);n&&n(e)}})}}bind(e,t){this.inputs.set(e,t)}bindToggle(e,t){this.toggleCallbacks.set(e,t)}bindDelta(e,t){this.deltaCallbacks.set(e,t)}unbind(e){this.inputs.delete(e),this.toggleCallbacks.delete(e),this.deltaCallbacks.delete(e)}setKeybind(e,t){this.keybinds.has(e)||this.keybinds.set(e,[]),this.keybinds.get(e).push(t)}setMousebind(e,t){this.mousebinds.set(e,t)}setTouchMoveBind(e,t){this.touchmoveBinds.set(e,t)}setWheelbind(e,t){this.wheelbinds.set(e,t)}setGamepadButtonBind(e,t){this.gamepadButtonBinds.has(e)||this.gamepadButtonBinds.set(e,[]),this.gamepadButtonBinds.get(e).push(t)}setGamepadAxisBind(e,t){this.gamepadAxisBinds.has(e)||this.gamepadAxisBinds.set(e,[]),this.gamepadAxisBinds.get(e).push(t)}removeKeybind(e){this.keybinds.delete(e)}removeMousebind(e){this.mousebinds.delete(e)}removeWheelbind(e){this.wheelbinds.delete(e)}removeGamepadButtonBind(e){this.gamepadButtonBinds.delete(e)}removeGamepadAxisBind(e){this.gamepadAxisBinds.delete(e)}vibrate(e,t=1,s=1){const i=navigator.getGamepads();for(const n of i)n&&n.vibrationActuator&&n.vibrationActuator.playEffect("dual-rumble",{startDelay:0,duration:e,weakMagnitude:s,strongMagnitude:t})}};a=e([t()],a);export{a as InputService};/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -1,4 +1,4 @@
1
- import{expect as t,test as e}from"vitest";import{ActionInput as o,ButtonInput as a,ToggleInput as i}from"../gameplay/input/input.js";e("button input uses hold mode by default",()=>{const e=new a,o=[];e.onStart(()=>o.push(e.activated)),e.onEnd(()=>o.push(e.activated)),e.toggle(!0),t(e.activated).toBe(!0),e.toggle(!0),t(o).toEqual([!0]),e.toggle(!1),t(e.activated).toBe(!1),t(o).toEqual([!0,!1])}),e("button input can switch to toggle mode at runtime",()=>{const e=new a,o=[];e.onStart(()=>o.push(e.activated)),e.onEnd(()=>o.push(e.activated)),e.mode="toggle",e.toggle(!0),t(e.activated).toBe(!1),e.toggle(!1),t(e.activated).toBe(!0),e.toggle(!1),t(e.activated).toBe(!1),t(o).toEqual([!0,!1])}),e("action and toggle inputs remain compatibility presets",()=>{const e=new o;e.toggle(!0),t(e.activated).toBe(!0),e.toggle(!1),t(e.activated).toBe(!1);const a=new i;a.toggle(!0),t(a.activated).toBe(!1),a.toggle(!1),t(a.activated).toBe(!0)});/*
1
+ import{expect as e,test as t,vi as a}from"vitest";import{ActionInput as o,ButtonInput as n,ToggleInput as s}from"../gameplay/input/input.js";a.mock("../gameplay/services/render.js",()=>({ViewController:class{}}));import{InputService as p}from"../gameplay/input/input-service.js";import{GamepadButtonBind as i}from"../gameplay/input/keybind.js";t("button input uses hold mode by default",()=>{const t=new n,a=[];t.onStart(()=>a.push(t.activated)),t.onEnd(()=>a.push(t.activated)),t.toggle(!0),e(t.activated).toBe(!0),t.toggle(!0),e(a).toEqual([!0]),t.toggle(!1),e(t.activated).toBe(!1),e(a).toEqual([!0,!1])}),t("button input can switch to toggle mode at runtime",()=>{const t=new n,a=[];t.onStart(()=>a.push(t.activated)),t.onEnd(()=>a.push(t.activated)),t.mode="toggle",t.toggle(!0),e(t.activated).toBe(!1),t.toggle(!1),e(t.activated).toBe(!0),t.toggle(!1),e(t.activated).toBe(!1),e(a).toEqual([!0,!1])}),t("action and toggle inputs remain compatibility presets",()=>{const t=new o;t.toggle(!0),e(t.activated).toBe(!0),t.toggle(!1),e(t.activated).toBe(!1);const a=new s;a.toggle(!0),e(a.activated).toBe(!1),a.toggle(!1),e(a.activated).toBe(!0)}),t("held gamepad buttons can be suppressed until released",()=>{const t={pressed:!0,value:1},a={connected:!0,mapping:"standard",buttons:[t],axes:[]},o=function(){const e=Object.create(p.prototype);return Object.assign(e,{view:{paused:!1},inputs:new Map,deltaCallbacks:new Map,toggleCallbacks:new Map,gamepadButtonBinds:new Map,gamepadAxisBinds:new Map,prevGamepadButtonStates:new Map,suppressedGamepadButtons:new Map,lastActiveDevice:"kbm",onDeviceChanged:{next:()=>{}}}),e}(),n=[];o.gamepadButtonBinds.set("jump",[new i(0)]),o.toggleCallbacks.set("jump",e=>n.push(e)),Object.defineProperty(navigator,"getGamepads",{configurable:!0,value:()=>[a]}),o.suppressHeldGamepadButtonsUntilRelease(),o.update(0),t.pressed=!1,t.value=0,o.update(0),t.pressed=!0,t.value=1,o.update(0),e(n).toEqual([!0])});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hology/core",
3
- "version": "0.0.221",
3
+ "version": "0.0.222",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",