{"id":23078076,"url":"https://github.com/insanj/oogycontrollermanager","last_synced_at":"2026-05-05T00:37:57.659Z","repository":{"id":57687988,"uuid":"476830302","full_name":"insanj/OogyControllerManager","owner":"insanj","description":"🎮 ⌨️  Typescript Gamepad and Keyboard handler consolidated into a tiny, easy-to-use npm package https://insanj.github.io/OogyControllerManager/","archived":false,"fork":false,"pushed_at":"2022-04-30T16:23:31.000Z","size":605,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-17T19:27:11.431Z","etag":null,"topics":["controller","ecmascript","es6","event","game","gamepad","handler","javascript","js","keyboard","module","npm","ts","typescript","vanilla","yarn"],"latest_commit_sha":null,"homepage":"https://insanj.github.io/OogyControllerManager/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/insanj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-01T18:32:27.000Z","updated_at":"2022-04-30T16:23:52.000Z","dependencies_parsed_at":"2022-08-25T17:02:14.224Z","dependency_job_id":null,"html_url":"https://github.com/insanj/OogyControllerManager","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insanj%2FOogyControllerManager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insanj%2FOogyControllerManager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insanj%2FOogyControllerManager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/insanj%2FOogyControllerManager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/insanj","download_url":"https://codeload.github.com/insanj/OogyControllerManager/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247000360,"owners_count":20867072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["controller","ecmascript","es6","event","game","gamepad","handler","javascript","js","keyboard","module","npm","ts","typescript","vanilla","yarn"],"created_at":"2024-12-16T10:46:26.239Z","updated_at":"2026-05-05T00:37:57.362Z","avatar_url":"https://github.com/insanj.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg width=\"300\" src=\"https://user-images.githubusercontent.com/951011/166113826-daec02fd-f2a6-441d-b599-6b3812763b85.png\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eOogyControllerManager\u003c/h1\u003e\n\u003ch3 align=\"center\"\u003e\u003ccode\u003eoogy-controller-manager\u003c/code\u003e\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n🎮 ⌨️  Typescript Gamepad and Keyboard handler consolidated into a tiny, easy-to-use npm package\n\n\u003c/p\u003e\n\n[Go to npm](https://www.npmjs.com/package/oogy-controller-manager)\n\n\u003ch2\u003eAbout\u003c/h2\u003e\n\nBuilt and used in production for [Oogy: Can You Help](https://oogycanyouhelp.com), an indie deckbuilding adventure on Steam (PC and Mac).\n\nThe goal of this project is to provide a useful abstraction over both the [Web Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API) and window-level [KeyboardEvents](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent).\n\nWhy? In this case, as a video game, we want to handle keyboard and controller input with consistency—particularly in special cases such as pause menu input, \"on controller disconnected\", and Steam Overlay handling.\n\n\u003ch2\u003eInstallation\u003c/h2\u003e\n\nIn Browser, use:\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { OogyControllerManager } from \"./OogyControllerManager.js\";\n  const controllerManager = new OogyControllerManager.OogyControllerManager({ // ...\n  // code goes here! see example/ for an example using index.html\n\u003c/script\u003e\n```\n\nIn Node environment, use:\n\n```bash\nnpm install -s oogy-controller-manager\n```\n\nAnd using:\n\n```ts\nimport { OogyControllerManager } from \"oogy-controller-manager\";\nconst controllerManager = new OogyControllerManager.OogyControllerManager({ // ...\n// see example/ for express/nodejs index.js usage\n```\n\n\u003ch4\u003e\u003ca href=\"example/\"\u003eSee example/ for demonstrations of both browser and Node implementations.\u003c/a\u003e\u003c/h4\u003e\n\n\u003e NOTE: the Typescript `src/` is bundled with the package, in case the transpiled `.js` is not suitable for all use-cases.\n\n\u003ch2\u003eBuilding\u003c/h2\u003e\n\nClone the repository, and run:\n\n```bash\nnpm install\nnpm start\n```\n\nThis will generate the latest production-ready `.js` file using Typescript, located at `dist/OogyControllerManager.js`.\n\nSee `package.json` for details. Configure `tsconfig.json` to build with sourcemaps.\n\n\u003ch2\u003eUsage\u003c/h2\u003e\n\n\u003ch3\u003eGetting Started\u003c/h3\u003e\n\n```ts\n/**\n * Represents an \"event listener\", primarily used to remove that listener\n when moving to a different screen. Has an automatically generated UUID,\n as well as a `deactivated` property, so may be helpful in order to detect\n if a current listener exists in a more complex context.\n*/\ncurrentControllerListener: OogyControllerListener;\n\ncontrollerManager: OogyControllerManager;\n\nconstructor() {\n  this.controllerManager = new OogyControllerManager({\n    onControllerDisconnected: () =\u003e {\n      this.handleUserPausedGame();\n      // if the controller is disconnected, simulate pause event\n    }\n  });\n\n  this.controllerManager.start(); // start receiving events\n\n  this.controllerManager.addRightClickListener({\n    // do the same with right clicking as pressing pause button, globally\n    onRightClick: () =\u003e {\n      this.handleUserPausedGameToggled();\n      // not same as `handleUserPausedGame`, as disconnect ALWAYS pauses,\n      // this can UNPAUSE as well\n    }\n  });\n\n  // click event firing can be disabled using the manager-level property:\n  // (this is what I use when the Steam Overlay is activated as well)\n  // this.controllerManager.shouldStopAcceptingClicks = true;\n}\n```\n\n\u003ch3\u003eBasic Usage\u003c/h3\u003e\n\n```ts\nstartUserInteraction(): void {\n\n  // in this example, we are on a hypothetical screen with several \"cards\".\n  // as we move on the keyboard or gamepad, we want to highlight the next card,\n  // until eventually, we press A to select a card\n\n  this.cardIndex = 0;\n  this.cards = [{}, {}, {}]; // example UI elements, most likely,\n  // that we are navigating with keyboard or controller\n\n  this.currentControllerListener = this.controllerManager.addListener({\n    onControllerInput: (input) =\u003e {\n      // here is where you may want to interrupt if you did not get a chance\n      // to either (1) remove or (2) deactivate the listener beforehand,\n      // or as a safeguard in case the screen is inactive but listener\n      // was not removed (yet) for some reason.\n      // example: if (this.suppressUserInteraction === true) {\n      //            return;\n      //          }\n      // note: this is not for pausing; see blocking listeners\n      // for handling pause (`handleUserPausedGame`)\n\n      switch (input) {\n        default:\n          return;\n        case OogyControllerInput.start:\n          this.handleUserPausedGameToggled();\n          // here is our 3rd path to pausing the game so far,\n          // and why this library can be helpful instead of doing this by hand\n          break;\n        case OogyControllerInput.a:\n          this.handleUserSelectedCard(this.cardIndex);\n          // SELECT the currently highlighted card so we can trigger any action, etc\n          break;\n        case OogyControllerInput.right:\n          this.highlightedCardIndex = this.highlightedCardIndex + 1;\n          // go to the next index -\u003e move one to the right\n          if (this.highlightedCardIndex \u003e this.cards.length - 1) {\n            this.highlightedCardIndex = 0; // wrap to start if already at end of list\n          }\n          break;\n        // .....\n      }\n    }\n  });\n}\n```\n\n\u003ch3\u003eDisconnecting\u003c/h3\u003e\n\n```ts\nstopUserInteraction(): void {\n  this.controllerManager.removeListener(\n    this.currentControllerListener\n  ); // this is all we need to do\n  // (each listener has an automagically generated UUID)\n}\n```\n\n\u003ch3\u003eBlocking Listeners\u003c/h3\u003e\n\n```ts\nstartNavigationBarBlockingListener(): void {\n\n  // in this example, we are entering a context where we need a \"blocking\"\n  // listener, that can intercept and get all keyboard/controller events\n  // without disrupting any existing listeners (such as, when PAUSE is active)\n\n  this.controllerManager.addBlockingListener({\n\n    // this is a special kind of listener that does not require a UUID,\n    // as the API only supports 1 blocking listener at a time\n    // (which is also its whole purpose, anything more complicated should\n    //  remove listeners or use the `deactivated` property...)\n    interceptControllerInputAndBlock: (input) =\u003e {\n\n      if (\n        input !== OogyControllerInput.guide \u0026\u0026\n        input !== OogyControllerInput.start\n      ) {\n\n        if (this.userPausedGame === true) {\n          // start is \"active\" so block anything else from receiving this\n          // input until start pressed again\n          this.handleInputWhileGamePaused(input);\n          return true;\n        }\n\n        return false; // not paused, so return `false` to NOT intercept\n      }\n\n      this.handleUserPausedGameToggled();\n      return true;\n      // return `true` to intercept, this is the guide or pause button\n    }\n\n  });\n}\n\nstopNavigationBarBlockingListener(): void {\n  this.controllerManager.removeBlockingListener();\n  // no uuid, so this removes the only blocking listener active\n}\n```\n\n\u003ch3\u003eKeyboard-Exclusive Listeners\u003c/h3\u003e\n\n```ts\nconst keyboardBlockingListener = this.controllerManager.addListener({\n  // when showing a virtual keyboard or directly processing\n  // keyboard input, we want to completely intercept keyboard events\n\n  interceptKeyboardInput: (keyboardEvent) =\u003e {\n    let keyCode = keyboardEvent.key.toUpperCase();\n    if (keyCode === \"CAPSLOCK\") {\n      /// ...\n    } else if (keyCode === \"ENTER\" || keyCode === \"ESCAPE\") {\n      this.handleEnterToggled();\n      // intercepting keyboard events has the side effect\n      // (in current API) of also disregarding inputs that we may\n      // have been using for navigation, such as ENTER and ESCAPE\n      return;\n    }\n  },\n\n  // we can still accept controller input while doing this\n  onControllerInput: (input) =\u003e {\n    // ...\n  },\n});\n```\n\n\u003cp align=\"center\"\u003e\n\u003cimg width=\"45%\" src=\"example/screenshot_browser.png\"\u003e\n\u003cimg width=\"45%\" src=\"example/screenshot_nodejs.png\"\u003e\n\u003c/p\u003e\n\n\u003ch2\u003eAuthors\u003c/h2\u003e\n\n```\nJulian (@insanj) Weiss\njulian@oogycanyouhelp.com\ngithub.com/insanj\n(c) 2022\n```\n\n\u003ch2\u003eLicense\u003c/h2\u003e\n\n```\nMIT License\n\nCopyright (c) 2022 Julian Weiss\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finsanj%2Foogycontrollermanager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finsanj%2Foogycontrollermanager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finsanj%2Foogycontrollermanager/lists"}