{"id":"biotoxin495/itemdisplayrenderer","name":"itemdisplayrenderer","scope":"biotoxin495","platform":"roblox","description":"A reusable UI rendering registry for Roblox.","version":"1.0.0","latest":"1.0.0","versions":["1.0.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"1a2646d1ef6c0d115f7acac498df3fc19f7dbd92eaf10d60bbc42d38a39b4b47","likes":0,"downloads":0,"install":"forest install biotoxin495/itemdisplayrenderer","url":"https://forest.dev/p/roblox/biotoxin495/itemdisplayrenderer","files":"https://api.forest.dev/ai/package/roblox/biotoxin495/itemdisplayrenderer/files","readme":"# ItemDisplayRenderer — A reusable UI rendering registry for Roblox\n\nA dependency-free, centralized item and reward visualization system for Roblox UI.\n\nItemDisplayRenderer lets a game register the visual behavior for a content type once, then reuse that renderer across reward screens, inventories, shops, quests, collectibles, purchase results, and other interfaces.\n\nThe module combines two common rendering workflows under one lifecycle:\n\n- **`Create()`** — the renderer creates/owns its display frame.\n- **`RenderInto()`** — the caller already owns the target frame and the renderer decorates it.\n\nBoth return the same render handle, support the same options, and clean up through the same lifecycle.\n\n## Quick example\n\nRegister a renderer once, then create its UI anywhere that needs to display that content type.\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal ItemDisplayRenderer = require(ReplicatedStorage.Packages.ItemDisplayRenderer)\n\nlocal Renderer = ItemDisplayRenderer.new()\n\nRenderer:Register(\"Coins\", {\n    Create = function()\n        return CoinTemplate:Clone()\n    end,\n\n    Render = function(context)\n        context.Frame.Amount.Text = tostring(context.Data.Amount)\n    end,\n})\n\nlocal handle = Renderer:Create(\"Coins\", RewardsContainer, {\n    Amount = 500,\n})\n```\n\n## 🚀 Features\n\n### One renderer, two workflows\n\nUse `Create()` when the renderer should create and own its display frame. Use `RenderInto()` when another system already owns the target frame. Both workflows return the same handle and use the same update and cleanup behavior.\n\n### Central renderer registry\n\nRegister visual behavior once and reuse it across reward screens, inventories, shops, quests, collectibles, and purchase results.\n\n### Deterministic lifecycle\n\nEvery render has a handle that can be inspected, updated, or destroyed. Cleanup runs when a render is updated, replaced, destroyed, or when its target `GuiObject` is destroyed.\n\n### Lists, variants, and templates\n\nRender ordered lists with duplicate renderer IDs, select presentation variants through display options, and create frames with functions or reusable templates.\n\n### Optional integrations\n\nPopup adapters, asynchronous work, and animated `ViewportFrame` renderers remain opt-in and dependency-free.\n\n### Fully typed, no dependencies\n\nItemDisplayRenderer uses strict Luau and does not require Kernel, Maid, Promise, Signal, or another framework.\n\n## 📖 Basic usage\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal ItemDisplayRenderer = require(\n    ReplicatedStorage.Packages.ItemDisplayRenderer\n)\n\nlocal Renderer = ItemDisplayRenderer.new()\n```\n\n### Register a renderer\n\nEvery renderer must define `Render(context)`.\n\nIf it should also be usable through `Create()`, give it either a `Create(context)` function, a `Template`, or a `Templates` table.\n\n```lua\nRenderer:Register(\"Coins\", {\n    Create = function(context)\n        local frame = CoinTemplate:Clone()\n        return frame\n    end,\n\n    Render = function(context)\n        context.Frame.Amount.Text = tostring(context.Data.Amount)\n\n        return function()\n            -- Optional cleanup.\n        end\n    end,\n})\n```\n\nThe cleanup function returned by `Render()` runs when the render is updated, replaced, destroyed, or when its target frame is destroyed.\n\n### `Renderer:Create(rendererId, parent, data, options?)`\n\nUse `Create()` when the renderer should create its own frame.\n\n```lua\nlocal handle = Renderer:Create(\"Coins\", RewardsContainer, {\n    Amount = 5000,\n})\n```\n\nThe resulting frame can be obtained from the handle:\n\n```lua\nlocal frame = handle:GetFrame()\n```\n\nBy default, a frame created through `Create()` is destroyed when its handle is destroyed.\n\n### `Renderer:RenderInto(rendererId, frame, data, options?)`\n\nUse `RenderInto()` when the surrounding system already owns the frame.\n\n```lua\nlocal handle = Renderer:RenderInto(\"Coins\", ExistingFrame, {\n    Amount = 5000,\n})\n```\n\nDestroying this handle runs renderer cleanup but keeps `ExistingFrame`, because the renderer does not own it.\n\nThis is useful for Studio-authored inventory slots, shop cards, reward entries, or any UI where the surrounding feature owns the layout.\n\n## Render handle API\n\nEvery successful render returns a handle.\n\n```lua\nlocal handle = Renderer:Create(...)\n```\n\n### Check its state\n\n```lua\nif handle:IsActive() then\n    print(\"Still active\")\nend\n```\n\n### Read its frame\n\n```lua\nlocal frame = handle:GetFrame()\n```\n\n### Update it\n\n```lua\nhandle:Update({\n    Amount = 10000,\n})\n```\n\n`Update()` retains the target GuiObject, cleans up the previous rendering lifecycle, then runs the renderer again with the new data.\n\nThis means every renderer automatically supports updates without needing a separate update callback.\n\n### Destroy it\n\n```lua\nhandle:Destroy()\n```\n\nThis runs:\n\n1. popup cleanup,\n2. renderer cleanup,\n3. automatic lifecycle disconnection,\n4. frame destruction if the renderer owns the frame.\n\n### Render replacement\n\nOnly one ItemDisplayRenderer render owns a target frame at a time.\n\nCalling `RenderInto()` on a frame that already has an active renderer automatically cleans up the old render before attaching the new one.\n\n```lua\nRenderer:RenderInto(\"Coins\", Slot, coinData)\nRenderer:RenderInto(\"Pet\", Slot, petData)\n```\n\nThe second call safely replaces the first render while preserving `Slot` itself.\n\nSet:\n\n```lua\n{\n    ReplaceExisting = false,\n}\n```\n\nif replacement should be rejected instead.\n\n### Ordered render lists\n\nLists use ordered request descriptors rather than renderer IDs as table keys.\n\n```lua\nlocal handles = Renderer:RenderList(RewardsContainer, {\n    {\n        Renderer = \"Coins\",\n        Data = { Amount = 500 },\n    },\n    {\n        Renderer = \"Coins\",\n        Data = { Amount = 2500 },\n    },\n    {\n        Renderer = \"Pet\",\n        Data = { Name = \"Golden Cat\" },\n    },\n})\n```\n\nBecause this is an array, duplicate renderer types are supported and ordering is deterministic.\n\n### Display variants\n\nPresentation-specific state belongs in display options rather than domain data.\n\n```lua\nRenderer:Create(\"Pet\", Container, {\n    Name = \"Golden Cat\",\n}, {\n    Variant = \"Expanded\",\n})\n```\n\nInside the renderer:\n\n```lua\nRender = function(context)\n    if context.Variant == \"Expanded\" then\n        -- Expanded presentation.\n    else\n        -- Default presentation.\n    end\nend\n```\n\nThis replaces patterns such as `_showExpanded` mixed into reward/item data.\n\n### Templates\n\nInstead of implementing `Create()`, a renderer can use a template:\n\n```lua\nRenderer:Register(\"Coins\", {\n    Template = CoinTemplate,\n\n    Render = function(context)\n        context.Frame.Amount.Text = tostring(context.Data.Amount)\n    end,\n})\n```\n\nOr variant-specific templates:\n\n```lua\nRenderer:Register(\"Pet\", {\n    Templates = {\n        Default = CompactPetTemplate,\n        Expanded = ExpandedPetTemplate,\n    },\n\n    Render = function(context)\n        -- Populate whichever template was selected.\n    end,\n})\n```\n\nThe module does not require UI to be built through code. Templates may be authored in Studio or created at runtime.\n\n### Popup and tooltip adapters\n\nPopups are optional and deliberately kept outside the core module.\n\nConstruct the renderer with an adapter:\n\n```lua\nlocal Renderer = ItemDisplayRenderer.new({\n    PopupAdapter = MyPopupAdapter,\n})\n```\n\nA renderer can then return a popup descriptor:\n\n```lua\nPopup = function(context)\n    return {\n        Type = \"CurrencyPopup\",\n        Data = {\n            CurrencyType = \"Coins\",\n            Amount = context.Data.Amount,\n        },\n    }\nend\n```\n\nEnable it per render:\n\n```lua\nRenderer:Create(\"Coins\", Container, data, {\n    Popup = true,\n})\n```\n\nThe adapter contract is:\n\n```lua\nfunction MyPopupAdapter:Register(frame, descriptor, context)\n    -- Attach hover/click behavior.\n\n    return function()\n        -- Remove it again.\n    end\nend\n```\n\n### Async-safe renderers\n\nSome renderers may need asynchronous work, such as avatar thumbnails or remote/config lookups.\n\nUse:\n\n```lua\ncontext:IsActive()\n```\n\nbefore applying delayed results.\n\n```lua\ntask.spawn(function()\n    local result = getSomethingAsync()\n\n    if context:IsActive() then\n        context.Frame.Icon.Image = result\n    end\nend)\n```\n\nThis prevents an old asynchronous operation from mutating a frame after that render has been replaced or destroyed.\n\n### `ViewportFrame` and animated renderers\n\nA renderer can create connections or run a 3D preview and return the matching cleanup function.\n\n```lua\nRender = function(context)\n    local connection = RunService.RenderStepped:Connect(function(dt)\n        -- Animate the preview.\n    end)\n\n    return function()\n        connection:Disconnect()\n        -- Clear the ViewportFrame.\n    end\nend\n```\n\nUpdating or destroying the render disconnects its `RenderStepped` connection through the returned cleanup function.\n\n### Renderer packs with `RegisterMany`\n\nRenderer packs can be grouped into separate modules and registered together.\n\n```lua\nRenderer:RegisterMany({\n    Coins = CoinRenderer,\n    Gems = GemRenderer,\n    Pet = PetRenderer,\n})\n```\n\nThis lets a larger project organize definitions into packages such as:\n\n```text\nRenderers/\n├── CurrencyRenderers.lua\n├── PetRenderers.lua\n├── CollectibleRenderers.lua\n└── PowerupRenderers.lua\n```\n\n## ⚙️ API\n\n### `ItemDisplayRenderer.new(config?)`\n\nCreates an independent renderer registry. Configuration may provide a popup adapter, enable strict errors, or change the default variant name.\n\n### Registry methods\n\n| Method | Description |\n| --- | --- |\n| `Register(rendererId, renderer, options?)` | Registers one renderer and returns whether registration succeeded |\n| `RegisterMany(renderers, options?)` | Registers a dictionary of renderer definitions |\n| `Unregister(rendererId)` | Removes a renderer registration |\n| `GetRenderer(rendererId)` | Returns the registered definition, if present |\n| `HasRenderer(rendererId)` | Reports whether an ID is registered |\n\nPass `{ Override = true }` to `Register` or `RegisterMany` to deliberately replace an existing registration.\n\n### Rendering methods\n\n| Method | Description |\n| --- | --- |\n| `Create(rendererId, parent, data, options?)` | Creates a frame, renders it, and returns its handle |\n| `RenderInto(rendererId, frame, data, options?)` | Renders into a caller-owned frame and returns its handle |\n| `RenderList(parent, requests, options?)` | Creates an ordered list and returns its successful handles |\n| `EndRendering(frame)` | Destroys the active handle associated with a frame |\n| `GetActiveHandle(frame)` | Returns the active handle associated with a frame |\n\n### Manager lifecycle methods\n\n| Method | Description |\n| --- | --- |\n| `SetPopupAdapter(adapter)` | Replaces or clears the popup adapter |\n| `Destroy()` | Destroys active handles, clears registrations, and releases the adapter |\n\n### Handle methods\n\n| Method | Description |\n| --- | --- |\n| `IsActive()` | Reports whether the handle is still active |\n| `GetFrame()` | Returns the rendered `GuiObject` |\n| `GetRendererId()` | Returns the registered renderer ID |\n| `GetData()` | Returns the handle's current data |\n| `GetOptions()` | Returns the resolved display options |\n| `Update(data, options?)` | Cleans up the current render and renders new state into the same frame |\n| `Destroy()` | Runs cleanup and destroys the frame when the handle owns it |\n\n## Complete options reference\n\nYou normally only need to provide the values that differ from the defaults.\n\n### Constructor configuration\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `PopupAdapter` | `PopupAdapter?` | Adapter used to attach popup or tooltip behavior |\n| `Strict` | `boolean?` | Raises errors instead of warnings for runtime rendering failures |\n| `DefaultVariant` | `string?` | Fallback variant name; defaults to `\"Default\"` |\n\n### Display options\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `Variant` | `string?` | Presentation variant exposed as `context.Variant` |\n| `Popup` | `boolean?` | Enables the renderer's popup descriptor for this render |\n| `ReplaceExisting` | `boolean?` | Allows replacement of an active render on the same frame; defaults to `true` |\n| `DestroyFrameOnCleanup` | `boolean?` | Controls whether a frame created by `Create()` is destroyed with its handle |\n| `Metadata` | `{ [any]: any }?` | Caller-defined presentation metadata available through `context.Options` |\n\n### Renderer definition\n\n| Member | Type | Description |\n| --- | --- | --- |\n| `Render` | `(context) -> cleanupFunction?` | Required function that applies data to the target frame |\n| `Create` | `(context) -> GuiObject?` | Optional factory used by `Create()` |\n| `Template` | `GuiObject?` | Optional default template cloned by `Create()` |\n| `Templates` | `{ [string]: GuiObject }?` | Optional templates indexed by variant name |\n| `Popup` | `(context) -> PopupDescriptor?` | Optional popup descriptor factory |\n\n### Display context\n\n| Member | Description |\n| --- | --- |\n| `RendererId` | ID of the active renderer |\n| `Frame` | Target `GuiObject`; `nil` only while a custom `Create` function is running |\n| `Parent` | Requested or current parent instance |\n| `Data` | Domain data supplied by the caller |\n| `Options` | Resolved display options |\n| `Variant` | Selected variant or configured default |\n| `Manager` | ItemDisplayRenderer instance managing the render |\n| `Handle` | Active render handle; `nil` while creating the frame |\n| `IsActive()` | Reports whether delayed work still belongs to an active render |\n\n## Architecture boundary\n\nItemDisplayRenderer is a presentation package.\n\nIt should not grant rewards or authoritatively mutate player data.\n\nA typical flow is:\n\n```text\nServer reward / inventory logic\n            │\n            ▼\n    item/reward descriptor\n            │\n            ▼\n          Client\n            │\n            ▼\n   ItemDisplayRenderer\n            │\n            ▼\n       Roblox UI\n```\n\nYour reward system determines that a player receives 500 Coins. ItemDisplayRenderer determines how those 500 Coins look in a given interface.\n\n## Migrating from the old two-manager design\n\nThe old `MainDisplayManager` use case maps to `RenderInto()`:\n\n```lua\nlocal handle = Renderer:RenderInto(\"Pet\", existingFrame, data, {\n    Popup = true,\n})\n```\n\nThe old `RewardVisualizerOnFrameManager` use case maps to `Create()`:\n\n```lua\nlocal handle = Renderer:Create(\"Pet\", rewardsContainer, data, {\n    Popup = true,\n})\n```\n\nBoth now share the same registry, options, handle type, popup integration, update behavior, and cleanup lifecycle.\n\n## Behavior\n\n> Register how something should appear once, then reuse that visual behavior anywhere.\n\nThe consuming feature owns its game logic and surrounding UI. ItemDisplayRenderer owns the visualization lifecycle.\n\n## 📝 Notes\n\n* A renderer must provide `Render(context)`.\n* `Create()` additionally requires `Create(context)`, `Template`, or `Templates`.\n* Only one active ItemDisplayRenderer render may own a target frame at a time.\n* Use `context:IsActive()` before applying delayed asynchronous results.\n* ItemDisplayRenderer is a presentation package; authoritative inventory and reward logic belongs elsewhere.\n* Call `Renderer:Destroy()` when the rendering context is no longer needed.\n\n## 🛠️ Installation\n\nCreate a `ModuleScript` named `ItemDisplayRenderer`, copy the contents of `init.luau` into it, and place it at:\n\n```text\nReplicatedStorage\n└── Packages\n    └── ItemDisplayRenderer\n```\n\nRequire the module from a client script wherever your packages are mapped.\n\n## License\n\nThis project is released under the MIT License.\n\nSee `LICENSE` for details.\n\nmade with ❤️ by biotoxin495\n","readmeTruncated":false}