@rbxts/app-forge 0.6.0 → 0.7.1

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.
Files changed (45) hide show
  1. package/README.md +334 -149
  2. package/out/global.d.ts +2 -3
  3. package/out/index.d.ts +11 -19
  4. package/out/init.luau +16 -115
  5. package/out/react/container.d.ts +10 -0
  6. package/out/{container.luau → react/container.luau} +13 -34
  7. package/out/react/context.d.ts +7 -0
  8. package/out/react/context.luau +12 -0
  9. package/out/{decorator.d.ts → react/decorator.d.ts} +5 -4
  10. package/out/{decorator.luau → react/decorator.luau} +6 -3
  11. package/out/react/helpers.d.ts +6 -0
  12. package/out/react/helpers.luau +74 -0
  13. package/out/react/hooks/useAppContext.d.ts +5 -0
  14. package/out/react/hooks/useAppContext.luau +16 -0
  15. package/out/react/index.d.ts +44 -0
  16. package/out/react/init.luau +123 -0
  17. package/out/react/rules/index.d.ts +6 -0
  18. package/out/react/rules/init.luau +27 -0
  19. package/out/react/rules/parent.d.ts +2 -0
  20. package/out/react/rules/parent.luau +42 -0
  21. package/out/react/types.d.ts +44 -0
  22. package/out/vide/classes/renders.d.ts +21 -0
  23. package/out/vide/classes/renders.luau +251 -0
  24. package/out/vide/classes/rules/exclusiveGroup.d.ts +2 -0
  25. package/out/vide/classes/rules/exclusiveGroup.luau +47 -0
  26. package/out/vide/classes/rules/index.d.ts +7 -0
  27. package/out/vide/classes/rules/init.luau +70 -0
  28. package/out/vide/classes/rules/parent.d.ts +2 -0
  29. package/out/vide/classes/rules/parent.luau +29 -0
  30. package/out/vide/context.d.ts +7 -0
  31. package/out/vide/context.luau +12 -0
  32. package/out/vide/decorator.d.ts +13 -0
  33. package/out/vide/decorator.luau +44 -0
  34. package/out/vide/hooks/useAppContext.d.ts +5 -0
  35. package/out/vide/hooks/useAppContext.luau +14 -0
  36. package/out/vide/index.d.ts +30 -0
  37. package/out/vide/init.luau +223 -0
  38. package/out/vide/types.d.ts +85 -0
  39. package/package.json +15 -16
  40. package/out/container.d.ts +0 -4
  41. package/out/helpers.d.ts +0 -2
  42. package/out/helpers.luau +0 -40
  43. package/out/rules.d.ts +0 -12
  44. package/out/rules.luau +0 -198
  45. package/out/types.d.ts +0 -59
package/README.md CHANGED
@@ -1,271 +1,456 @@
1
- # AppForge — React App & Window Manager for roblox-ts
1
+ # AppForge
2
2
 
3
- AppForge is a UI/app orchestration system for React in Roblox, enabling:
3
+ > ⚠️ **Documentation Notice**
4
+ >
5
+ > This README was written with the assistance of **AI tooling**.
6
+ > I do not currently have the time to write a fully hand-crafted documentation site, so there may be typos, rough wording, or missing explanations.
7
+ >
8
+ > If you find an issue, inconsistency, or bug in the docs or API, please **@ me on Discord** in the **roblox-ts Discord**: `@loner71x`.
9
+ >
10
+ > Thank you for your patience ❤️
4
11
 
5
- App registration
6
- ✔ Visibility & state control
7
- App grouping
8
- ✔ Exclusive UI modes
9
- Rule-based interactions
10
- ✔ Priority & layering
11
- ✔ React-friendly state bindings & animations
12
+ > **An App Manager for Vide**
13
+
14
+ AppForge is a **declarative UI application manager** built on top of [Vide](https://github.com/centau/vide) for **roblox-ts**. It provides a structured way to register, mount, show/hide, group, and coordinate UI "apps" using rules instead of ad‑hoc state wiring.
15
+
16
+ If you’ve ever ended up with tangled UI state, duplicated visibility logic, or brittle parent/child UI dependencies — AppForge is meant to solve that without forcing you into complex patterns.
17
+
18
+ ---
19
+
20
+ ## ✨ Features
21
+
22
+ * **App-based UI architecture**
23
+ * **Centralized visibility state** per app
24
+ * **Rule system** (parent/child, exclusive groups, z-index)
25
+ * **Render groups** for selective mounting
26
+ * **px scaling** built-in via `loners-pretty-vide-utils`
27
+ * **Vide context integration**
28
+ * **Story / sandbox rendering**
29
+ * Fully typed with **roblox-ts**
12
30
 
13
31
  ---
14
32
 
15
- # 📦 Installation
33
+ ## 📦 Installation
16
34
 
17
35
  ```bash
18
- npm i @rbxts/app-forge
19
- # or
20
- bun i @rbxts/app-forge
36
+ bun add @rbxts/app-forge
21
37
  ```
22
38
 
39
+ Peer dependencies:
40
+
41
+ ```bash
42
+ bun add @rbxts/vide @rbxts/loners-pretty-vide-utils
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🧠 Core Concepts
48
+
49
+ ### App
50
+
51
+ An **App** is a self-contained UI unit:
52
+
53
+ * owns its own visibility source
54
+ * renders a Vide tree
55
+ * can depend on other apps via rules
56
+
57
+ Apps are registered using a decorator and rendered through `AppForge`.
58
+
59
+ ---
60
+
61
+ ### Forge
62
+
63
+ `AppForge` is the runtime manager. It:
64
+
65
+ * owns all app visibility sources
66
+ * mounts and unmounts UI
67
+ * enforces rules
68
+ * exposes imperative helpers (`open`, `close`, `toggle`)
69
+
70
+ You usually create **one Forge per UI root**.
71
+
72
+ ---
73
+
74
+ ### Rules
75
+
76
+ Rules define **relationships between apps**, not layout.
77
+
78
+ Currently supported:
79
+
80
+ | Rule | Description |
81
+ | ---------------- | -------------------------------------- |
82
+ | `parent` | Child app closes when parent closes |
83
+ | `detach` | Prevents automatic anchoring to parent |
84
+ | `exclusiveGroup` | Only one app in the group may be open |
85
+ | `index` | Sets ZIndex of the app container |
86
+
87
+ Rules are enforced automatically whenever visibility changes.
88
+
23
89
  ---
24
90
 
25
- # 🧩 Setup — REQUIRED
91
+ ### Render Groups
26
92
 
27
- ### Create `global.d.ts`
93
+ Render groups let you **selectively mount apps**.
94
+
95
+ Example use cases:
96
+
97
+ * Lobby UI vs In‑Game UI
98
+ * HUD vs Menus
99
+ * Feature‑flagged UI
100
+
101
+ ---
102
+
103
+ ## 🧩 Basic Usage
104
+
105
+ ### Creating a Forge
28
106
 
29
107
  ```ts
30
- declare global {
31
- type AppProps = {
32
- player: Player;
33
- }
34
- export {};
35
- }
108
+ const forge = new CreateVideForge();
36
109
  ```
37
110
 
38
- > App names are now autodetected via decoratorsno need to define `AppNames` globally.
111
+ > `CreateVideForge` owns its internal state. You **do not pass the forge into itself** it is only provided to Apps at render-time.
39
112
 
40
113
  ---
41
114
 
42
- # 🚀 Initializing AppForge
115
+ ### Mounting (Game Runtime)
116
+
117
+ AppForge is mounted once from application bootstrap code (commonly a Flamework controller).
43
118
 
44
119
  ```ts
45
- import { Workspace } from "@rbxts/services"
120
+ const forge = new CreateVideForge();
121
+
122
+ forge.mount(
123
+ () => (
124
+ <screengui
125
+ Name="App"
126
+ ZIndexBehavior="Sibling"
127
+ ResetOnSpawn={false}
128
+ />
129
+ ),
130
+ {
131
+ props,
132
+ },
133
+ Players.LocalPlayer.WaitForChild("PlayerGui"),
134
+ );
135
+ ```
46
136
 
47
- import { createPortal, createRoot } from "@rbxts/react-roblox";
48
- import { Players, Workspace } from "@rbxts/services";
49
- import AppForge, { Render } from "@rbxts/app-forge";
50
- import React from "@rbxts/react";
137
+ Notes:
51
138
 
52
- const forge = new AppForge();
53
- const root = createRoot(new Instance("Folder"));
139
+ * `forge` is **implicitly available** to Apps
140
+ * `props` are user-defined and become `this.props` inside Apps
141
+ * visibility & rules are controlled entirely by the Forge
54
142
 
55
- const target = Workspace.CurrentCamera!
143
+ ---
144
+
145
+ ### Mounting (Game Runtime)
56
146
 
57
- const props = {
58
- player: Players.LocalPlayer!,
59
- } as const satisfies AppProps;
147
+ AppForge is typically mounted from a **controller** (e.g. Flamework) and targets `PlayerGui`.
60
148
 
61
- root.render(
62
- createPortal(
63
- <screengui ResetOnSpawn={false} ZIndexBehavior="Sibling">
64
- <Render {...{ props, forge, root, target }} />
65
- </screengui>,
66
- Players.LocalPlayer!.WaitForChild("PlayerGui")!,
149
+ ```ts
150
+ const forge = new CreateVideForge();
151
+
152
+ forge.mount(
153
+ () => (
154
+ <screengui
155
+ Name="App"
156
+ ZIndexBehavior="Sibling"
157
+ ResetOnSpawn={false}
158
+ />
67
159
  ),
160
+ {
161
+ props,
162
+ },
163
+ Players.LocalPlayer.WaitForChild("PlayerGui"),
68
164
  );
69
165
  ```
70
166
 
71
- > `Render` will control all decorated apps.
167
+ This:
168
+
169
+ * creates a single root `ScreenGui`
170
+ * mounts all rendered apps under it
171
+ * keeps AppForge in control of visibility & rules
72
172
 
73
173
  ---
74
174
 
75
- # 🧱 Creating an App
175
+ ### Mounting
76
176
 
77
177
  ```ts
78
- import { App, Args } from "@rbxts/app-forge";
79
- import React from "@rbxts/react";
178
+ forge.mount(
179
+ () => <screengui ResetOnSpawn={false} />,
180
+ {
181
+ props: {},
182
+ },
183
+ playerGui,
184
+ );
185
+ ```
186
+
187
+ ---
188
+
189
+ ### Opening & Closing Apps
80
190
 
81
- @App({
82
- name: "SideButtons",
83
- visible: true,
191
+ ```ts
192
+ forge.open("Inventory");
193
+ forge.close("Inventory");
194
+ forge.toggle("Inventory");
195
+ ```
196
+
197
+ You can also access the reactive source directly:
198
+
199
+ ```ts
200
+ const visible = forge.getSource("Inventory");
201
+ ```
202
+
203
+ ---
204
+
205
+ ## 🧱 Defining an App
206
+
207
+ ```ts
208
+ import { VideApp, VideArgs } from "@rbxts/app-forge";
209
+ import Vide from "@rbxts/vide";
210
+
211
+ @VideApp({
212
+ name: "Inventory",
213
+ renderGroup: "Lobby",
214
+ visible: false,
215
+ rules: {
216
+ index: 2,
217
+ },
84
218
  })
85
- export default class SideButtons extends Args {
86
- public render() {
87
- return <frame Size={UDim2.fromScale(1, 1)}>Side Buttons UI</frame>;
219
+ export class Inventory extends VideArgs {
220
+ render() {
221
+ const { px } = this.props;
222
+
223
+ return (
224
+ <frame
225
+ BackgroundColor3={Color3.fromRGB(100, 100, 100)}
226
+ Size={() => UDim2.fromOffset(px(500), px(500))}
227
+ />
228
+ );
88
229
  }
89
230
  }
90
231
  ```
91
232
 
92
233
  ---
93
234
 
94
- # 📦 Props & `Args` Features
95
-
96
- Inside a decorated App:
235
+ ## 🔗 Parent / Child Apps
97
236
 
98
237
  ```ts
99
- this.forge // AppForge instance
100
- this.name // App Name
101
- this.props // AppProps
102
- this.bind // visible state bind
238
+ @VideApp({
239
+ name: "InventoryInfo",
240
+ renderGroup: "Lobby",
241
+ rules: {
242
+ parent: "Inventory",
243
+ },
244
+ })
245
+ export class InventoryInfo extends VideArgs {
246
+ render() {
247
+ return <frame />;
248
+ }
249
+ }
103
250
  ```
104
251
 
105
- Example:
252
+ Behavior:
253
+
254
+ * When `Inventory` closes → `InventoryInfo` closes
255
+ * Child is **anchored** to parent unless `detach: true`
106
256
 
107
257
  ```ts
108
- const { px } = this.props;
109
- const scale2px = px.map((s) => s * 2);
258
+ rules: {
259
+ parent: "Inventory",
260
+ detach: true,
261
+ }
110
262
  ```
111
263
 
112
264
  ---
113
265
 
114
- # 🕹 App Manager API
266
+ ## 🚦 Exclusive Groups
115
267
 
116
268
  ```ts
117
- forge.toggle("Inventory");
118
- forge.set("Shop", true);
119
- forge.set("HUD", false);
120
- const shown = forge.getState("Pause");
121
- const bind = forge.getBind("Inventory");
269
+ @VideApp({
270
+ name: "Settings",
271
+ rules: {
272
+ exclusiveGroup: "Menus",
273
+ },
274
+ })
122
275
  ```
123
276
 
277
+ Only one app in the same `exclusiveGroup` may be open at a time.
278
+
124
279
  ---
125
280
 
126
- # 📐 Using `getBind()` inside React
281
+ ## 🎭 Render Control
127
282
 
128
- ```tsx
129
- return (
130
- <frame Visible={forge.getBind("Inventory")}>
131
- Items…
132
- </frame>
133
- );
134
- ```
283
+ AppForge supports **multiple render selection modes**. You can render by:
284
+
285
+ * a single app name
286
+ * multiple app names
287
+ * one or more render groups
288
+ * combinations of `group + name(s)`
289
+
290
+ All render options are passed via `VideRenderProps`.
135
291
 
136
292
  ---
137
293
 
138
- # ⌨️ Hotkey Toggle Example
294
+ ### Render a single app
139
295
 
140
296
  ```ts
141
- UserInputService.InputBegan.Connect((input) => {
142
- if (input.KeyCode === Enum.KeyCode.I)
143
- forge.toggle("Inventory");
144
- });
297
+ render: { name: "Inventory" }
145
298
  ```
146
299
 
147
300
  ---
148
301
 
149
- # ⚖️ APP RULES SYSTEM
302
+ ### Render multiple apps
150
303
 
151
- Rules control app visibility behavior.
152
-
153
- | Rule | Effect |
154
- | --------- | --------------------------------------- |
155
- | blockedBy | Prevents opening if another is open |
156
- | blocks | Closes another app when opened |
157
- | exclusive | Closes ALL other apps except same group |
158
- | groups | Non-conflicting coexistence grouping |
159
- | Core | Always allowed — never auto-closed |
160
- | layer | (Reserved – future rendering priority) |
304
+ ```ts
305
+ render: { names: ["Inventory", "InventoryInfo"] }
306
+ ```
161
307
 
162
308
  ---
163
309
 
164
- ## `groups`
310
+ ### Render by group
165
311
 
166
312
  ```ts
167
- @App({ name: "HUD", rules: { groups: "HUD" } })
168
- @App({ name: "Crosshair", rules: { groups: "HUD" } })
313
+ render: { group: "Lobby" }
169
314
  ```
170
315
 
171
- Both may open at the same time.
172
-
173
316
  ---
174
317
 
175
- ## `Core`
318
+ ### Render by group + name
176
319
 
177
320
  ```ts
178
- @App({ name: "FPSCounter", rules: { groups: "Core" } })
321
+ render: {
322
+ group: "Lobby",
323
+ name: "Inventory",
324
+ }
179
325
  ```
180
326
 
181
- Never closed by rules.
327
+ Only renders `Inventory` **if** it belongs to the `Lobby` group.
182
328
 
183
329
  ---
184
330
 
185
- # 🧪 Modern App Example (from your snippet)
331
+ ### Render by group + names
186
332
 
187
333
  ```ts
188
- @App({ name: "TestApp", visible: true, rules: { groups: "Core" } })
189
- export default class TestApp extends Args {
190
- public render() {
191
- const { px } = this.props;
192
-
193
- return (
194
- <frame AnchorPoint={new Vector2(0.5, 1)}>
195
- UI Stuff…
196
- </frame>
197
- );
198
- }
334
+ render: {
335
+ group: "Lobby",
336
+ names: ["Inventory", "Settings"],
199
337
  }
200
338
  ```
201
339
 
340
+ Only renders apps that:
341
+
342
+ * are in the specified group(s)
343
+ * and whose names match the provided list
344
+
202
345
  ---
203
346
 
204
- # 🧠 Using AppForge from Flamework
347
+ ## 🧪 Story / Sandbox Rendering
348
+
349
+ AppForge provides `forge.story` for **isolated rendering**, commonly used with **UI Labs**.
205
350
 
206
351
  ```ts
207
- import AppForge, { Render } from "@rbxts/app-forge";
208
-
209
- @Controller()
210
- export default class AppController implements OnInit {
211
- onInit() {
212
- const props = this.createProps(player);
213
- const forge = new AppForge();
214
- const root = createRoot(new Instance("Folder"));
215
-
216
- root.render(
217
- createPortal(
218
- <screengui ResetOnSpawn={false}>
219
- <Render {...{ props, forge, root, target: props.target }} />
220
- </screengui>,
221
- player.WaitForChild("PlayerGui"),
222
- ),
223
- );
224
- }
225
- }
352
+ const forge = new CreateVideForge();
353
+
354
+ return forge.story({
355
+ forge,
356
+ props,
357
+ config: {
358
+ px: {
359
+ target: storyProps.target,
360
+ },
361
+ },
362
+ render: { group: "Lobby" },
363
+ });
226
364
  ```
227
365
 
366
+ This is ideal for:
367
+
368
+ * component stories
369
+ * previews
370
+ * controlled visibility via bindings
371
+
228
372
  ---
229
373
 
230
- # 🧠 Using with Storybook/Setup
374
+ ## 🧠 Context Access Inside Apps
231
375
 
232
- You can manually choose which apps render:
376
+ App props are provided via Vide context.
377
+
378
+ ```ts
379
+ import { Provider } from "@rbxts/vide";
380
+ import { VideContexts } from "@rbxts/app-forge";
233
381
 
234
- ```tsx
235
- <Render {...{ props, forge, target, name: "TestApp" }} />
382
+ <Provider context={VideContexts.App} value={this.props}>
383
+ {() => <Child />}
384
+ </Provider>
236
385
  ```
237
386
 
238
- or:
387
+ Or via hook:
388
+
389
+ ````ts
390
+ ```ts
391
+ import { useVideAppContext } from "@rbxts/app-forge";
392
+
393
+ const app = useVideAppContext();
394
+ ````
239
395
 
240
- ```tsx
241
- <Render {...{ props, forge, target, names: ["HUD", "Shop"] }} />
396
+ ---
397
+
398
+ ## 🧱 Architecture Overview
399
+
400
+ ```
401
+ AppForge
402
+ ├─ AppRegistry (static)
403
+ ├─ Visibility Sources
404
+ ├─ Render Manager
405
+ ├─ Rule Engine
406
+ │ ├─ Parent Rule
407
+ │ └─ Exclusive Group Rule
408
+ └─ Vide Mount
242
409
  ```
243
410
 
244
411
  ---
245
412
 
246
- # Best Practices
413
+ ## ⚠️ Notes
247
414
 
248
- Use `groups` for compatible UIs
249
- Use `blockedBy` to avoid interruptions
250
- Use `blocks` for mutual exclusion
251
- Use `exclusive` for fullscreen control
252
- ✔ Use `"Core"` for never-hidden persistent UI
253
- ✔ Avoid manually instantiating apps
415
+ * Apps are **singletons per Forge**
416
+ * Rendering twice will warn if px is re‑initialized
417
+ * Rules are enforced **reactively**
418
+ * This package is currently **alpha** — APIs may change
254
419
 
255
420
  ---
256
421
 
257
- # 🛠 Future Roadmap
422
+ ## 🛣 Roadmap
258
423
 
259
- * [ ] UI layering & depth priority
424
+ * [ ] Transition animations API
425
+ * [ ] Async app loading
426
+ * [ ] Better dev warnings
427
+ * [ ] Debug inspector
260
428
 
261
429
  ---
262
430
 
263
- # ❤️ Contributing
431
+ ## ⚛️ React Support (Planned)
432
+
433
+ AppForge is designed as a **renderer-agnostic App Manager**.
434
+
435
+ Currently:
436
+
437
+ * ✅ **Vide renderer** is production-ready
438
+ * 🚧 **React renderer** exists but is **very early / experimental**
264
439
 
265
- PRs and suggestions welcome!
440
+ React support is intentionally paused while the Vide API stabilizes. The author is still learning React, and decided to refocus on Vide first. React will be revisited once the core architecture is fully locked in.
441
+
442
+ Public surface (subject to change):
443
+
444
+ ```ts
445
+ import { ReactApp, ReactArgs, CreateReactForge } from "@rbxts/app-forge";
446
+ ```
447
+
448
+ **Vide is the recommended and supported path today.**
266
449
 
267
450
  ---
268
451
 
269
- # 📄 License
452
+ ## 📜 License
270
453
 
271
454
  MIT
455
+
456
+ ---
package/out/global.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  declare global {
2
2
  // These will be overridden by the user
3
3
  // They are only placeholders for your build
4
-
5
- type AppGroups = readonly string[];
6
- type AppNames = readonly string[];
4
+ type GroupNames = string;
5
+ type AppNames = string;
7
6
  type AppProps = {};
8
7
  }
9
8
  export {};
package/out/index.d.ts CHANGED
@@ -1,19 +1,11 @@
1
- import Vide from "@rbxts/vide";
2
- import type Types from "./types";
3
- import { Args, App } from "./decorator";
4
- export default class AppForge {
5
- sources: Map<string, Vide.Source<boolean>>;
6
- loaded: Map<string, Vide.Node>;
7
- private rulesManager;
8
- getSource(name: AppNames[number]): Vide.Source<boolean>;
9
- set(name: AppNames[number], value: Vide.Source<boolean> | boolean): void;
10
- open(name: AppNames[number]): void;
11
- close(name: AppNames[number]): void;
12
- toggle(name: AppNames[number]): void;
13
- renderApp(props: Types.NameProps & Types.MainProps): Vide.Node;
14
- renderApps(props: Types.NameProps & Types.MainProps): Vide.Node[];
15
- renderAll(props: Types.MainProps): Vide.Node[];
16
- }
17
- export { App, Args };
18
- export { Render } from "./helpers";
19
- export type { NameProps, MainProps, ClassProps } from "./types";
1
+ export { App as ReactApp, Args as ReactArgs } from "./react/decorator";
2
+ export { App as VideApp, Args as VideArgs } from "./vide/decorator";
3
+ export { default as CreateReactForge } from "./react";
4
+ export { default as CreateVideForge } from "./vide";
5
+ export { Render as RenderReact } from "./react/helpers";
6
+ export type { MainProps as VideProps, ClassProps as VideClassProps, RenderProps as VideRenderProps, } from "./vide/types";
7
+ export type { MainProps as ReactProps, ClassProps as ReactClassProps, } from "./react/types";
8
+ export { default as useReactAppContext } from "./react/hooks/useAppContext";
9
+ export { default as ReactContexts } from "./react/context";
10
+ export { default as useVideAppContext } from "./vide/hooks/useAppContext";
11
+ export { default as VideContexts } from "./vide/context";