{"id":"biotoxin495/loadingspinner","name":"loadingspinner","scope":"biotoxin495","platform":"roblox","description":"A small, dependency-free Roblox utility for displaying animated loading spinners over UI elements.","version":"1.0.4","latest":"1.0.4","versions":["1.0.0","1.0.1","1.0.2","1.0.3","1.0.4"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"0c403d04c0d999c1e6d6497d4797542f1302c60fa45fd360f4041f3b6cde2927","likes":0,"downloads":0,"install":"forest install biotoxin495/loadingspinner","url":"https://forest.dev/p/roblox/biotoxin495/loadingspinner","files":"https://api.forest.dev/ai/package/roblox/biotoxin495/loadingspinner/files","readme":"# LoadingSpinner — A small animated loading spinner module\n\n**LoadingSpinner** is a small, dependency-free Roblox utility for displaying animated loading spinners over UI elements.\n\nIt creates a dimmed overlay inside any `GuiObject`, fades in a configurable icon, and rotates it until the loading state is cleared. Each target can have one active spinner, and every spinner is represented by a handle that can be hidden or destroyed independently.\n\n## Quick example\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal LoadingSpinner = require(ReplicatedStorage.LoadingSpinner)\nlocal spinners = LoadingSpinner.new()\n\nlocal target = script.Parent\nlocal handle = spinners:Show(target)\n\ntask.wait(2)\nhandle:Hide()\n```\n\n`Hide` fades the spinner out before cleaning it up. To remove it immediately instead, call `handle:Destroy()`.\n\n## 🚀 Features\n\n* Fully standalone and dependency-free\n* Attach a spinner to any `GuiObject`\n* One active spinner per target\n* Per-instance default configuration\n* Per-spinner configuration overrides\n* Animated fade-in and fade-out\n* Configurable icon, size, color, rotation speed, and direction\n* Optional input blocking\n* Optional show delay to avoid brief loading flashes\n* Optional minimum visible duration\n* Per-spinner handles\n* `IsLoading`, `SetLoading`, and `HideAll` helpers\n* Automatic cleanup when a target is destroyed\n* Optional custom UI factory\n* Strict Luau types\n\n## 📖 Basic usage\n\nCopy `LoadingSpinner.luau` into your project and require it from a client script.\n\nLoadingSpinner creates and animates Roblox UI, so it should normally be used from the client.\n\n### Configuring defaults\n\nPass a configuration table to `LoadingSpinner.new` to define defaults for that controller.\n\n```lua\nlocal spinners = LoadingSpinner.new({\n\tIcon = \"rbxassetid://15734250582\",\n\tIconColor = Color3.fromRGB(255, 255, 255),\n\tIconSize = UDim2.fromOffset(36, 36),\n\n\tOverlayColor = Color3.fromRGB(0, 0, 0),\n\tOverlayTransparency = 0.4,\n\n\tSpinDuration = 1.2,\n\tFadeDuration = 0.15,\n\tShowDelay = 0.1,\n\tMinimumVisibleDuration = 0.2,\n})\n```\n\nEvery call to `Show` can override those defaults:\n\n```lua\nlocal handle = spinners:Show(script.Parent, {\n\tIconColor = Color3.fromRGB(80, 180, 255),\n\tOverlayTransparency = 0.25,\n})\n```\n\n### State-driven usage\n\n`SetLoading` is convenient when the spinner follows an existing boolean state.\n\n```lua\nspinners:SetLoading(container, true)\n\nlocal success, result = pcall(loadData)\n\nspinners:SetLoading(container, false)\n```\n\nYou can query whether a target currently owns an active or pending spinner:\n\n```lua\nif spinners:IsLoading(container) then\n\tprint(\"The container is loading\")\nend\n```\n\nA spinner waiting for its configured `ShowDelay` still counts as loading.\n\n## ⚙️ API\n\n### Controller\n\n#### `LoadingSpinner.new(defaultConfig?)`\n\nCreates a new LoadingSpinner controller. Each controller owns its own configuration and active spinner registry.\n\n```lua\nlocal spinners = LoadingSpinner.new()\n```\n\n#### `spinners:Show(target, config?)`\n\nCreates and returns a spinner handle for `target`.\n\n```lua\nlocal handle = spinners:Show(frame)\n```\n\nIf the target already has a spinner owned by this controller, the old spinner is destroyed before the new one is created.\n\n#### `spinners:Hide(target)`\n\nFades out the spinner associated with `target`.\n\n```lua\nspinners:Hide(frame)\n```\n\nIf the spinner is still waiting for its `ShowDelay`, it is cancelled without being shown.\n\n#### `spinners:HideAll()`\n\nFades out every spinner owned by the controller.\n\n```lua\nspinners:HideAll()\n```\n\n#### `spinners:IsLoading(target)`\n\nReturns whether the target has an active or pending spinner.\n\n```lua\nlocal isLoading = spinners:IsLoading(frame)\n```\n\n#### `spinners:SetLoading(target, isLoading, config?)`\n\nShows or hides a spinner based on a boolean.\n\n```lua\nspinners:SetLoading(frame, true, {\n\tShowDelay = 0.15,\n})\n\nspinners:SetLoading(frame, false)\n```\n\nWhen `isLoading` is true, the method returns the active spinner handle. Repeated `true` updates reuse the current handle instead of restarting its animation. When `isLoading` is false, the method returns `nil`.\n\n#### `spinners:Destroy()`\n\nImmediately destroys every spinner owned by the controller and makes the controller unusable.\n\n```lua\nspinners:Destroy()\n```\n\n### Spinner handle\n\n#### `handle:Hide()`\n\nFades the spinner out and then destroys it. The configured `MinimumVisibleDuration` is respected before the fade begins.\n\n#### `handle:Destroy()`\n\nImmediately stops all animations, removes the UI, and unregisters the spinner. It is safe to call more than once.\n\n#### `handle:IsActive()`\n\nReturns `true` while the spinner is pending, visible, or fading out.\n\n#### `handle:IsVisible()`\n\nReturns `true` after the spinner UI has been created. This remains true while it is fading out.\n\n#### `handle:GetTarget()`\n\nReturns the target `GuiObject`, or `nil` after the handle has been destroyed.\n\n#### `handle:GetUI()`\n\nReturns the generated overlay `Frame`, or `nil` if the spinner has not appeared yet or has already been destroyed.\n\n## Complete configuration reference\n\n| Property | Type | Default | Description |\n| --- | --- | --- | --- |\n| `Name` | `string` | `\"LoadingSpinner\"` | Name assigned to the generated overlay. |\n| `ZIndex` | `number` | `100` | Z-index of the overlay. The indicator uses `ZIndex + 1`. |\n| `Icon` | `string` | Included spinner asset | Image used by the spinner. |\n| `IconColor` | `Color3` | White | Color applied to the spinner image. |\n| `IconSize` | `UDim2` | `32 x 32` | Size of the spinner image. |\n| `SpinDuration` | `number` | `1.5` | Seconds required for one complete rotation. |\n| `RotationDirection` | `number` | `1` | Positive values rotate clockwise; negative values rotate counter-clockwise. |\n| `OverlayColor` | `Color3` | Black | Color of the dimming overlay. |\n| `OverlayTransparency` | `number` | `0.5` | Visible transparency of the overlay, clamped from 0 to 1. |\n| `BlockInput` | `boolean` | `true` | Sets the overlay's `Active` property so it can intercept input. |\n| `FadeDuration` | `number` | `0.1` | Fade-in and fade-out duration in seconds. |\n| `ShowDelay` | `number` | `0` | Delay before creating the spinner. Useful for avoiding flashes during fast operations. |\n| `MinimumVisibleDuration` | `number` | `0` | Minimum time the spinner remains visible before hiding. |\n| `Create` | `function` | `nil` | Optional custom UI factory. |\n\nNegative duration values are treated as 0. `SpinDuration` is kept above zero so that the rotation tween remains valid.\n\n## Avoiding loading flashes\n\nVery fast operations can briefly display and immediately remove a spinner. `ShowDelay` prevents the spinner from appearing unless the operation lasts long enough.\n\n```lua\nlocal spinners = LoadingSpinner.new({\n\tShowDelay = 0.15,\n\tMinimumVisibleDuration = 0.2,\n})\n```\n\nWith this configuration:\n\n* Operations shorter than 0.15 seconds do not display a spinner.\n* Once displayed, the spinner remains visible for at least 0.2 seconds.\n\n## Custom UI\n\nUse `Create` when the default overlay structure does not match your project.\n\nThe callback receives the resolved configuration and must return:\n\n* An unparented `Frame` used as the overlay.\n* An `ImageLabel` used as the rotating indicator.\n\n```lua\nlocal spinners = LoadingSpinner.new({\n\tCreate = function(config)\n\t\tlocal overlay = Instance.new(\"Frame\")\n\t\toverlay.Name = config.Name or \"LoadingSpinner\"\n\t\toverlay.Size = UDim2.fromScale(1, 1)\n\t\toverlay.BorderSizePixel = 0\n\t\toverlay.BackgroundColor3 = config.OverlayColor or Color3.new(0, 0, 0)\n\t\toverlay.ZIndex = config.ZIndex or 100\n\n\t\tlocal indicator = Instance.new(\"ImageLabel\")\n\t\tindicator.AnchorPoint = Vector2.new(0.5, 0.5)\n\t\tindicator.Position = UDim2.fromScale(0.5, 0.5)\n\t\tindicator.Size = config.IconSize or UDim2.fromOffset(32, 32)\n\t\tindicator.BackgroundTransparency = 1\n\t\tindicator.Image = config.Icon or \"\"\n\t\tindicator.ImageColor3 = config.IconColor or Color3.new(1, 1, 1)\n\t\tindicator.ZIndex = (config.ZIndex or 100) + 1\n\t\tindicator.Parent = overlay\n\n\t\treturn overlay, indicator\n\tend,\n})\n```\n\nLoadingSpinner ensures the indicator belongs to the returned overlay, then manages parenting, rotation, fading, and cleanup after the callback returns.\n\n## 📝 Notes\n\n* A spinner is automatically unregistered when its handle is hidden and the fade completes, its handle is destroyed, its target is destroyed, its generated overlay is destroyed externally, or its controller is destroyed.\n* All cleanup methods are idempotent, so repeated calls are safe.\n* LoadingSpinner is intended for client-side UI.\n\n## 🛠️ Installation\n\nCopy `LoadingSpinner.luau` into your project — for example under `ReplicatedStorage`:\n\n```text\nReplicatedStorage\n└── LoadingSpinner\n```\n\nThen require it from a client script:\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal LoadingSpinner = require(ReplicatedStorage.LoadingSpinner)\n```\n\n## License\n\nAdd the license used by your project before publishing the module.\n\n\nmade with ❤️ by biotoxin495\n","readmeTruncated":false}