{"id":"marcosdly/mitt","name":"mitt","scope":"marcosdly","platform":"roblox","description":"Mirrored from the Wally registry.","version":"1.0.0","latest":"1.0.0","versions":["1.0.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":["License identified from the packaged LICENSE file; the manifest declared none."],"licenseVerified":true,"dependencies":{},"integrity":"4f37e4fb607fbb6ad8def18054a31e61db8e1f29bf3d8cfe1dd6338fe970bc75","likes":0,"downloads":0,"install":"forest install marcosdly/mitt","url":"https://forest.dev/p/roblox/marcosdly/mitt","files":"https://api.forest.dev/ai/package/roblox/marcosdly/mitt/files","readme":"<div align=\"center\">\r\n\r\n# Mitt\r\n\r\n**Full-stack input library for Roblox**\r\n\r\n[![Version](https://img.shields.io/badge/version-2.0.0-6C3EF4?style=for-the-badge&logoColor=white)](https://github.com/NotKisoMomo/Mitt)\r\n[![Stability](https://img.shields.io/badge/stability-stable-22c55e?style=for-the-badge)](https://github.com/NotKisoMomo/Mitt)\r\n[![License](https://img.shields.io/badge/license-MIT-6C3EF4?style=for-the-badge)](https://github.com/NotKisoMomo/Mitt/blob/main/LICENSE)\r\n[![Luau](https://img.shields.io/badge/luau-typed-6C3EF4?style=for-the-badge)](https://luau-lang.org)\r\n[![Roblox](https://img.shields.io/badge/roblox-client--only-e11d48?style=for-the-badge)](https://create.roblox.com)\r\n[![Built by Plinko Labs](https://img.shields.io/badge/built%20by-Plinko%20Labs-6C3EF4?style=for-the-badge)](https://github.com/NotKisoMomo)\r\n\r\n</div>\r\n\r\n---\r\n\r\nMitt is a full-stack input library for Roblox. It provides a unified action registry, multi-driver backend (UIS / IAS / CAS), context-aware filtering, combo and shortcut detection, Promise-based async flows, session-based state containers, a mobile touch layer with icon library, and raw input access -- all in one client-side module.\r\n\r\n---\r\n\r\n## Table of Contents\r\n\r\n* [Features](#features)\r\n* [Installation](#installation)\r\n* [Quick Start](#quick-start)\r\n* [Core Concepts](#core-concepts)\r\n  * [How Mitt Thinks About Input](#how-mitt-thinks-about-input)\r\n  * [Actions and Handles](#actions-and-handles)\r\n  * [Drivers](#drivers)\r\n  * [Type Inference](#type-inference)\r\n  * [Context Stack](#context-stack)\r\n  * [Signals vs Promises](#signals-vs-promises)\r\n  * [Sessions](#sessions)\r\n* [API Reference](#api-reference)\r\n  * [Mitt.define](#mittdefine)\r\n  * [Action Handle](#action-handle)\r\n  * [Signals](#signals)\r\n  * [Promises](#promises)\r\n  * [Context](#context)\r\n  * [Sessions](#sessions-api)\r\n  * [Touch](#touch)\r\n  * [Icons](#icons)\r\n  * [Platform](#platform)\r\n  * [Async Operators](#async-operators)\r\n  * [Raw Input](#raw-input)\r\n  * [Helpers](#helpers)\r\n  * [Registry](#registry)\r\n  * [Debug](#debug)\r\n* [Patterns](#patterns)\r\n  * [Charge Attack](#charge-attack)\r\n  * [Input Buffering](#input-buffering)\r\n  * [Context Switching](#context-switching)\r\n  * [Session-driven Combat State](#session-driven-combat-state)\r\n  * [IAS Direction2D Movement](#ias-direction2d-movement)\r\n  * [IAS Analog Trigger](#ias-analog-trigger)\r\n  * [Mobile Layout with Icons](#mobile-layout-with-icons)\r\n  * [Platform-adaptive Bindings](#platform-adaptive-bindings)\r\n  * [Settings Screen Rebinding](#settings-screen-rebinding)\r\n  * [Tutorial Gating](#tutorial-gating)\r\n* [Combat Example](#combat-example)\r\n* [Exported Types](#exported-types)\r\n* [Changelog](#changelog)\r\n* [Contact](#contact)\r\n\r\n---\r\n\r\n## Features\r\n\r\n**Core**\r\n* **Unified Action Registry:** Define once with `Mitt.define`, get a handle back. No string lookups, no global tables.\r\n* **Multi-driver Backend:** `\"uis\"` (default), `\"ias\"` (native IAS instances), or `\"cas\"` (ContextActionService) -- per-action, all returning identical handles.\r\n* **Auto-inferred Action Types:** Mitt reads your `bindings` and decides `button` vs `axis` automatically.\r\n* **Context Stack:** Push `\"menu\"` and gameplay actions go silent. Pop it and everything resumes -- no listener changes.\r\n\r\n**Action Modes**\r\n* **Button / Axis / Combo / Shortcut** -- original modes, unchanged.\r\n* **Hold:** Fires `triggered` only after continuously held for `holdTime` seconds.\r\n* **DoubleTap:** Two presses within `window` -- no sequence config needed.\r\n* **Charge:** Built-in charge mode -- fires `charged` each tick with normalized `0-1` progress, `released` carries final level.\r\n* **Mash:** Counts rapid presses in a window, fires `triggered` with a `count` field when threshold met.\r\n* **LongPress:** Mobile-first -- fires after `n` seconds held, cancels on early release.\r\n* **Gesture:** Directional swipe detection on touch/mouse -- up/down/left/right/diagonal.\r\n* **Sequence:** Alias for combo with added `stepFired` signal on each valid step.\r\n\r\n**IAS Driver**\r\n* Creates real `InputAction`, `InputContext`, `InputBinding` instances under the hood.\r\n* Full `InputActionType` support: `Bool`, `Direction1D`, `Direction2D`, `Direction3D`, `ViewportPosition`.\r\n* Composite direction bindings: WASD → dir2d, trigger analog, 6DOF keys.\r\n* Analog threshold control, response curves, axis scale, modifier keys, clamp magnitude.\r\n* `stateChanged`, `bindingsChanged` signals. `:getState()`, `:getBindings()`, `:fire()`.\r\n* Context priority and sink fully wired to native `InputContext`.\r\n\r\n**CAS Driver**\r\n* Full CAS bind/unbind lifecycle managed by Mitt.\r\n* `casPriority`, `processSink`, and `mobile` callback exposed on config.\r\n\r\n**Mobile Touch Layer**\r\n* `Mitt.touch.layout()` -- declare positions, Mitt creates and wires buttons.\r\n* `Mitt.touch.bind()` -- manually wire your own button instances.\r\n* `Mitt.touch.register()` -- self-registration by action name, UI and input fully decoupled.\r\n* Context-aware visibility -- buttons auto-show/hide with their action's context.\r\n* Draggable layout with `Mitt.touch.exportLayout()` / `importLayout()` for persistence.\r\n* `Mitt.touch.theme()` -- configure idle/pressed colors, corner radius, icon tint, label font.\r\n\r\n**Icon Library**\r\n* `Mitt.Icons` -- flat table of named asset ID tokens covering combat, movement, interaction, UI, weapons, vehicle, and ability slots.\r\n* `Mitt.Icons.Custom(assetId)` -- bring your own ID into the same pipeline.\r\n\r\n**Async Operators**\r\n* `Mitt.gate`, `Mitt.debounce`, `Mitt.throttle`, `Mitt.map`, `Mitt.after`, `Mitt.any`, `Mitt.repeat`.\r\n\r\n**Platform**\r\n* `Mitt.platform.current` -- reactive, fires `changed` on device switch.\r\n* Per-binding `platforms` filter -- bindings only activate on matching hardware.\r\n* `preferredInput` integration -- auto-promotes correct bindings on device switch.\r\n\r\n**Sessions**\r\n* Named state containers. `onChange`, `onAnyChange`, `onBatch`. Derive, link, persist, schema-type.\r\n\r\n**Rebinding & Profiles**\r\n* Named profiles: `exportProfile`, `importProfile`, `diffBindings`, `resetToDefaults`.\r\n* Conflict detection: `Mitt.getConflicts()`. Binding lock: `action:lockBinding()`.\r\n\r\n**Debug & Tooling**\r\n* `Mitt.record()` / `Mitt.replay()` -- deterministic input recording and replay.\r\n* `Mitt.audit()` -- report of unused, unbound, and conflicting actions.\r\n* `Mitt.profile()` -- signal dispatch latency per action.\r\n* `Mitt.visualizer()` -- live overlay with context stack, session state, and binding conflicts.\r\n* Action tags: `tags = { \"combat\" }`, `Mitt.getByTag(\"combat\")`.\r\n\r\n---\r\n\r\n## Installation\r\n\r\nPlace the Mitt folder into `ReplicatedStorage`, then require it on the client. Mitt is a **client-only** library. Never require it from a server Script.\r\n\r\n```lua\r\nlocal Mitt = require(game.ReplicatedStorage.Mitt)\r\n```\r\n\r\n---\r\n\r\n## Quick Start\r\n\r\n```lua\r\nlocal Mitt = require(game.ReplicatedStorage.Mitt)\r\n\r\nlocal Jump = Mitt.define(\"Jump\", {\r\n    bindings = { Enum.KeyCode.Space, Enum.KeyCode.ButtonA },\r\n    contexts = { \"gameplay\" },\r\n})\r\n\r\nlocal Look = Mitt.define(\"Look\", {\r\n    bindings = { Enum.UserInputType.MouseMovement },\r\n    contexts = { \"gameplay\" },\r\n    deadzone = 1.5,\r\n})\r\n\r\nlocal Dash = Mitt.define(\"Dash\", {\r\n    contexts = { \"gameplay\" },\r\n    mode     = \"combo\",\r\n    sequence = { Enum.KeyCode.W, Enum.KeyCode.W },\r\n    window   = 0.3,\r\n})\r\n\r\nlocal State = Mitt.createSession(\"Movement\", {\r\n    jumping  = false,\r\n    dashing  = false,\r\n    airborne = false,\r\n})\r\n\r\nJump.pressed:connect(function(event)\r\n    event:consume()\r\n    State:set(\"jumping\", true)\r\n    character:Jump()\r\nend)\r\n\r\nJump.released:connect(function()\r\n    State:set(\"jumping\", false)\r\nend)\r\n\r\nLook.moved:connect(function(event)\r\n    camera:ApplyDelta(event.delta)\r\nend)\r\n\r\nDash.triggered:connect(function()\r\n    State:set(\"dashing\", true)\r\n    character:Dash()\r\n    task.delay(0.4, function()\r\n        State:set(\"dashing\", false)\r\n    end)\r\nend)\r\n\r\nMitt.context.push(\"gameplay\")\r\n```\r\n\r\n---\r\n\r\n## Core Concepts\r\n\r\n### How Mitt Thinks About Input\r\n\r\nWithout Mitt, input in Roblox looks like this:\r\n\r\n```lua\r\nlocal buffered = false\r\nlocal charging = false\r\nlocal blocking = false\r\n\r\nUserInputService.InputBegan:Connect(function(input, gameProcessed)\r\n    if gameProcessed then return end\r\n    if input.KeyCode == Enum.KeyCode.Space then\r\n        charging = true\r\n        character:Jump()\r\n    end\r\nend)\r\n```\r\n\r\nOnce you have 20 actions, multiple game states, combos, held detection, rebinding, and transient booleans scattered across modules -- it becomes unmaintainable.\r\n\r\nMitt solves both sides. Actions replace the `InputBegan` wall. Sessions replace the scattered state variables.\r\n\r\n---\r\n\r\n### Actions and Handles\r\n\r\n`Mitt.define` registers an action and returns a handle. Hold onto it -- it is the only way to interact with that action. No retrieval-by-string by design.\r\n\r\n```lua\r\nlocal Jump = Mitt.define(\"Jump\", {\r\n    bindings = { Enum.KeyCode.Space },\r\n    contexts = { \"gameplay\" },\r\n})\r\n\r\nJump.pressed:connect(function(event)\r\n    character:Jump()\r\nend)\r\n\r\nreturn { Jump = Jump }\r\n```\r\n\r\n---\r\n\r\n### Drivers\r\n\r\nEvery action has a `driver` field that controls which native system owns raw input registration.\r\n\r\n| Driver | Backend | When to use |\r\n|--------|---------|-------------|\r\n| `\"uis\"` | UserInputService (default) | Most cases -- no instances needed |\r\n| `\"ias\"` | InputAction/Context/Binding instances | Cross-platform, analog, directional, touch UIButton |\r\n| `\"cas\"` | ContextActionService | Legacy CAS integration, mobile button callbacks |\r\n\r\nThe handle you get back is identical regardless of driver -- same signals, sessions, combo engine, everything.\r\n\r\n---\r\n\r\n### Type Inference\r\n\r\nMitt reads your `bindings` and classifies the action:\r\n\r\n| Binding | Inferred type |\r\n|---------|--------------|\r\n| Any `KeyCode` except thumbsticks | `button` |\r\n| `MouseButton1/2/3` | `button` |\r\n| `MouseMovement`, `MouseWheel` | `axis` |\r\n| `Thumbstick1`, `Thumbstick2` | `axis` |\r\n| `Gyro`, `Accelerometer` | `axis` |\r\n\r\nOverride with an explicit `mode` field if needed.\r\n\r\n---\r\n\r\n### Context Stack\r\n\r\nLIFO list of named strings. An action fires only if its `contexts` contains something in the stack. Actions with no `contexts` always fire.\r\n\r\n```lua\r\nMitt.context.push(\"gameplay\")\r\nMitt.context.push(\"menu\")\r\n-- both gameplay and menu actions fire\r\n\r\nMitt.context.pop()\r\n-- back to gameplay only\r\n```\r\n\r\n---\r\n\r\n### Signals vs Promises\r\n\r\n**Signals** -- persistent, fires every time.\r\n\r\n```lua\r\nJump.pressed:connect(function(event)\r\n    character:Jump()\r\nend)\r\n```\r\n\r\n**Promises** -- one-shot, self-cleaning.\r\n\r\n```lua\r\nJump.pressed:once():andThen(function(event)\r\n    print(\"first jump\")\r\nend)\r\n```\r\n\r\n---\r\n\r\n### Sessions\r\n\r\nNamed state containers that replace scattered module-level variables. Every key change fires a signal.\r\n\r\n```lua\r\nlocal CombatState = Mitt.createSession(\"Combat\", {\r\n    charging  = false,\r\n    blocking  = false,\r\n    comboStep = 0,\r\n})\r\n```\r\n\r\n---\r\n\r\n## API Reference\r\n\r\n### Mitt.define\r\n\r\n```lua\r\nMitt.define(name: string, config: ActionConfig) -> ActionHandle\r\n```\r\n\r\nRegisters an action and returns its handle. Errors if `name` is already registered.\r\n\r\n| Field | Type | Default | Description |\r\n|-------|------|---------|-------------|\r\n| `bindings` | `{ InputBinding \\| BindingConfig }` | required | Input sources. Omit for combos. |\r\n| `contexts` | `{ string }?` | nil | Active contexts. Omit to always fire. |\r\n| `driver` | `DriverMode?` | `\"uis\"` | `\"uis\"` \\| `\"ias\"` \\| `\"cas\"` |\r\n| `mode` | `ActionMode?` | inferred | Override action mode. |\r\n| `actionType` | `ActionType?` | `\"bool\"` | IAS only: `\"bool\"` \\| `\"dir1d\"` \\| `\"dir2d\"` \\| `\"dir3d\"` \\| `\"viewport\"` |\r\n| `sequence` | `{ InputBinding }?` | nil | Combo/sequence mode steps. |\r\n| `window` | `number?` | `0.4` | Combo timing window in seconds. |\r\n| `holdTime` | `number?` | `0.5` | Seconds for `hold`/`longpress` mode. |\r\n| `mashThreshold` | `number?` | `5` | Press count for `mash` mode. |\r\n| `deadzone` | `number \\| DeadzoneConfig?` | `0.0` | Flat magnitude or `{ inner, outer }`. |\r\n| `repeatInterval` | `number?` | nil | Held re-fire interval. |\r\n| `priority` | `number?` | nil | IAS/CAS bind priority. |\r\n| `sink` | `boolean?` | false | IAS: block lower-priority contexts. |\r\n| `platforms` | `{ string }?` | nil | `\"keyboard\"` \\| `\"gamepad\"` \\| `\"touch\"` filter. |\r\n| `tags` | `{ string }?` | nil | Tag grouping for `Mitt.getByTag`. |\r\n| `casPriority` | `number?` | nil | CAS only: bind priority. |\r\n| `processSink` | `boolean?` | false | CAS only: sink gameProcessed. |\r\n| `mobile` | `((UserInputState) -> ())?` | nil | CAS only: touch button callback. |\r\n\r\n**BindingConfig** (IAS driver, object instead of KeyCode):\r\n\r\n| Field | Type | Description |\r\n|-------|------|-------------|\r\n| `keyCode` | `KeyCode \\| UserInputType` | Primary key |\r\n| `primaryModifier` | `KeyCode?` | Required held modifier |\r\n| `secondaryModifier` | `KeyCode?` | Second required modifier |\r\n| `pressedThreshold` | `number?` | Analog press threshold |\r\n| `releasedThreshold` | `number?` | Analog release threshold |\r\n| `responseCurve` | `number?` | Easing on analog value |\r\n| `scale` | `number?` | Scalar multiplier |\r\n| `vector2Scale` | `Vector2?` | Per-axis 2D scale |\r\n| `vector3Scale` | `Vector3?` | Per-axis 3D scale |\r\n| `clampMagnitude` | `boolean?` | Normalize composite input |\r\n| `pointerIndex` | `number?` | Multi-touch finger index |\r\n| `uiButton` | `GuiButton?` | Touch button (bool type only) |\r\n| `up/down/left/right` | `KeyCode?` | Composite dir2d/dir1d keys |\r\n| `forward/backward` | `KeyCode?` | Composite dir3d keys |\r\n| `invertAxis` | `boolean?` | Flip axis sign |\r\n| `platforms` | `{ string }?` | Per-binding platform filter |\r\n\r\n---\r\n\r\n### Action Handle\r\n\r\nThe object returned by `Mitt.define`.\r\n\r\n---\r\n\r\n### Signals\r\n\r\n| Signal | Fires when | Modes |\r\n|--------|-----------|-------|\r\n| `pressed` | Binding goes down | `button`, `shortcut`, `hold`, `charge`, `longpress`, `doubletap` |\r\n| `released` | Binding goes up | `button`, `charge` |\r\n| `held` | Binding held per Heartbeat | `button` |\r\n| `moved` | Axis delta | `axis`, `dir2d`, `dir3d`, `viewport` |\r\n| `triggered` | Combo/shortcut/hold/mash completes | `combo`, `sequence`, `shortcut`, `hold`, `mash`, `doubletap`, `gesture` |\r\n| `charged` | Each tick during charge -- value `0-1` | `charge` |\r\n| `stateChanged` | Any value change | `ias` driver all types |\r\n| `stepFired` | Each valid combo step | `combo`, `sequence` |\r\n| `canceled` | Combo/charge broken early | `combo`, `sequence`, `charge` |\r\n| `conflicted` | Binding claimed by higher-priority action | all |\r\n| `bindingsChanged` | Binding added/removed at runtime | `ias` driver |\r\n| `enabledChanged` | Action enabled state flips | all |\r\n| `contextChanged` | Action's context pushed/popped | all |\r\n\r\n```lua\r\nlocal disconnect = Jump.pressed:connect(function(event)\r\n    character:Jump()\r\nend)\r\n\r\ndisconnect()\r\n\r\nJump.charged:connect(function(progress)\r\n    ChargeBar:SetFill(progress)\r\nend)\r\n\r\nMove.stateChanged:connect(function(value)\r\n    -- Vector2 for dir2d\r\n    Animator:SetBlend(value.Magnitude)\r\nend)\r\n```\r\n\r\n#### InputEvent fields\r\n\r\n| Field | Type | Description |\r\n|-------|------|-------------|\r\n| `binding` | `KeyCode \\| UserInputType` | Specific binding that fired |\r\n| `phase` | `\"pressed\" \\| \"released\" \\| \"changed\"` | Input phase |\r\n| `position` | `Vector2?` | Screen position |\r\n| `delta` | `Vector2?` | Movement delta |\r\n| `held` | `boolean` | True on repeat-mode ticks |\r\n| `consumed` | `boolean` | Readonly -- whether consumed |\r\n\r\n**`:consume()`** stops propagation to remaining subscribers.\r\n\r\n---\r\n\r\n### State Helpers\r\n\r\n```lua\r\nJump:isHeld()             -- boolean\r\nJump:heldDuration()       -- number\r\nJump:getCharge()          -- number -- 0-1, 0 if not charging\r\nJump:getState()           -- boolean | number | Vector2 | Vector3  (ias driver)\r\nJump:getBindings()        -- { InputBinding }  (ias driver)\r\nJump:getPriority()        -- number\r\nJump:set","readmeTruncated":true,"readmeFull":"https://api.forest.dev/v1/package/marcosdly/roblox/mitt/1.0.0/readme"}