@rpgjs/client 5.0.0-beta.27 → 5.0.0-beta.28

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 (105) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/Game/AnimationManager.js.map +1 -1
  3. package/dist/Game/ClientVisuals.js.map +1 -1
  4. package/dist/Game/Map.js +2 -2
  5. package/dist/Game/Map.js.map +1 -1
  6. package/dist/Game/Object.js +1 -1
  7. package/dist/Game/Object.js.map +1 -1
  8. package/dist/Game/ProjectileManager.js.map +1 -1
  9. package/dist/Gui/Gui.d.ts +34 -15
  10. package/dist/Gui/Gui.js +40 -22
  11. package/dist/Gui/Gui.js.map +1 -1
  12. package/dist/Gui/NotificationManager.js.map +1 -1
  13. package/dist/RpgClient.d.ts +3 -64
  14. package/dist/RpgClientEngine.js +1 -1
  15. package/dist/RpgClientEngine.js.map +1 -1
  16. package/dist/_virtual/{_@oxc-project_runtime@0.133.0 → _@oxc-project_runtime@0.139.0}/helpers/esm/decorate.js +2 -2
  17. package/dist/_virtual/{_@oxc-project_runtime@0.133.0 → _@oxc-project_runtime@0.139.0}/helpers/esm/decorateMetadata.js +2 -2
  18. package/dist/components/animations/animation.ce.js.map +1 -1
  19. package/dist/components/animations/fx.ce.js.map +1 -1
  20. package/dist/components/animations/hit.ce.js.map +1 -1
  21. package/dist/components/character-hitbox.js +1 -1
  22. package/dist/components/character.ce.js +3 -1
  23. package/dist/components/character.ce.js.map +1 -1
  24. package/dist/components/dynamics/bar.ce.js.map +1 -1
  25. package/dist/components/dynamics/image.ce.js.map +1 -1
  26. package/dist/components/dynamics/parse-value.js +1 -1
  27. package/dist/components/dynamics/parse-value.js.map +1 -1
  28. package/dist/components/dynamics/shape-utils.js +1 -1
  29. package/dist/components/dynamics/shape.ce.js.map +1 -1
  30. package/dist/components/dynamics/text.ce.js.map +1 -1
  31. package/dist/components/gui/box.ce.js.map +1 -1
  32. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  33. package/dist/components/gui/gameover.ce.js.map +1 -1
  34. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  35. package/dist/components/gui/index.js +17 -16
  36. package/dist/components/gui/input-field.ce.js.map +1 -1
  37. package/dist/components/gui/input.ce.js.map +1 -1
  38. package/dist/components/gui/menu/equip-menu.ce.js +1 -1
  39. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  40. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  41. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  42. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  43. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  44. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  45. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  46. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  47. package/dist/components/gui/save-load.ce.js.map +1 -1
  48. package/dist/components/gui/shop/shop.ce.js +4 -2
  49. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  50. package/dist/components/gui/title-screen.ce.js.map +1 -1
  51. package/dist/components/index.js +4 -3
  52. package/dist/components/interaction-components.ce.js.map +1 -1
  53. package/dist/components/player-components-utils.js +1 -1
  54. package/dist/components/player-components-utils.js.map +1 -1
  55. package/dist/components/player-components.ce.js.map +1 -1
  56. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  57. package/dist/components/prebuilt/index.js +3 -2
  58. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  59. package/dist/components/scenes/canvas.ce.js.map +1 -1
  60. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  61. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  62. package/dist/components/scenes/weather-tick-lifecycle.js.map +1 -1
  63. package/dist/core/setup.js.map +1 -1
  64. package/dist/module.js.map +1 -1
  65. package/dist/node_modules/.pnpm/@signe_di@3.1.0/node_modules/@signe/di/dist/index.js +1 -1
  66. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js +1 -1
  67. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js.map +1 -1
  68. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js +2 -2
  69. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js.map +1 -1
  70. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/client/index.js +1 -1
  71. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js +1 -1
  72. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js.map +1 -1
  73. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js +2 -2
  74. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js.map +1 -1
  75. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js +1 -1
  76. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js.map +1 -1
  77. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js +7 -7
  78. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js.map +1 -1
  79. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js +1 -1
  80. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js +2 -2
  81. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js.map +1 -1
  82. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js.map +1 -1
  83. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js.map +1 -1
  84. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js +1 -1
  85. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js.map +1 -1
  86. package/dist/presets/faceset.js.map +1 -1
  87. package/dist/presets/rmspritesheet.js.map +1 -1
  88. package/dist/services/cameraFollow.js +1 -1
  89. package/dist/services/cameraFollow.js.map +1 -1
  90. package/dist/services/interactions.js.map +1 -1
  91. package/dist/services/mapStreaming.js.map +1 -1
  92. package/dist/services/mmorpg-connection.js +1 -1
  93. package/dist/services/mmorpg-connection.js.map +1 -1
  94. package/dist/services/mmorpg.js.map +1 -1
  95. package/dist/services/pointerContext.js +2 -1
  96. package/dist/services/pointerContext.js.map +1 -1
  97. package/dist/services/save.js.map +1 -1
  98. package/dist/services/standalone.js.map +1 -1
  99. package/dist/utils/getEntityProp.js.map +1 -1
  100. package/dist/utils/syncHitbox.js.map +1 -1
  101. package/package.json +9 -9
  102. package/src/Gui/Gui.spec.ts +26 -1
  103. package/src/Gui/Gui.ts +101 -37
  104. package/src/RpgClient.ts +2 -63
  105. package/src/public-api-types.spec.ts +19 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/client",
3
- "version": "5.0.0-beta.27",
3
+ "version": "5.0.0-beta.28",
4
4
  "description": "RPGJS is a framework for creating RPG/MMORPG games",
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,14 +17,14 @@
17
17
  "author": "Samuel Ronce",
18
18
  "license": "MIT",
19
19
  "peerDependencies": {
20
- "@canvasengine/presets": "^2.0.0",
21
- "canvasengine": "^2.0.1",
20
+ "@canvasengine/presets": "^2.1.0",
21
+ "canvasengine": "^2.1.1",
22
22
  "pixi.js": "^8.9.2"
23
23
  },
24
24
  "dependencies": {
25
25
  "@rpgjs/common": "5.0.0-beta.26",
26
- "@rpgjs/server": "5.0.0-beta.27",
27
- "@rpgjs/ui-css": "5.0.0-beta.23",
26
+ "@rpgjs/server": "5.0.0-beta.28",
27
+ "@rpgjs/ui-css": "5.0.0-beta.24",
28
28
  "@signe/di": "3.1.0",
29
29
  "@signe/room": "3.1.0",
30
30
  "@signe/sync": "3.1.0",
@@ -32,10 +32,10 @@
32
32
  "rxjs": "^7.8.2"
33
33
  },
34
34
  "devDependencies": {
35
- "@canvasengine/compiler": "2.0.0",
36
- "vite": "^8.0.16",
37
- "vite-plugin-dts": "^5.0.2",
38
- "vitest": "^4.1.9"
35
+ "@canvasengine/compiler": "2.1.0",
36
+ "vite": "^8.1.5",
37
+ "vite-plugin-dts": "^5.0.3",
38
+ "vitest": "^4.1.10"
39
39
  },
40
40
  "type": "module",
41
41
  "scripts": {
@@ -176,6 +176,29 @@ describe("RpgGui Vue integration", () => {
176
176
  expect(gui.getAttachedVueGuis().map(item => item.name)).toEqual(["tooltip"]);
177
177
  });
178
178
 
179
+ test("records explicit renderer ownership for both GUI registries", async () => {
180
+ const { gui } = await createGui();
181
+
182
+ gui.add({
183
+ id: "explicit-canvas",
184
+ component: CanvasGui,
185
+ renderer: "canvas",
186
+ });
187
+ gui.add({
188
+ id: "explicit-vue",
189
+ component: VueInventory,
190
+ renderer: "vue",
191
+ });
192
+
193
+ expect(gui.getAll()["explicit-canvas"].renderer).toBe("canvas");
194
+ expect(gui.getVueGuis()).toEqual([
195
+ expect.objectContaining({
196
+ name: "explicit-vue",
197
+ renderer: "vue",
198
+ }),
199
+ ]);
200
+ });
201
+
179
202
  test("synchronizes Vue GUI display and hide states through the Vue bridge", async () => {
180
203
  const { gui } = await createGui();
181
204
  const bridge = {
@@ -334,7 +357,9 @@ describe("RpgGui Vue integration", () => {
334
357
 
335
358
  gui.guiInteraction(PrebuiltGui.MainMenu, "useItem", { id: "potion" });
336
359
 
337
- expect(gui.get(PrebuiltGui.MainMenu)?.data().items).toEqual([
360
+ expect(gui.get<{ items: Array<{ id: string; quantity: number }> }>(
361
+ PrebuiltGui.MainMenu
362
+ )?.data().items).toEqual([
338
363
  {
339
364
  id: "potion",
340
365
  quantity: 1,
package/src/Gui/Gui.ts CHANGED
@@ -1,16 +1,28 @@
1
1
  import { Context, inject } from "@signe/di";
2
- import { signal, Signal, WritableSignal } from "canvasengine";
2
+ import { signal, Signal, WritableSignal, type ComponentFunction } from "canvasengine";
3
3
  import { AbstractWebsocket, WebSocketToken } from "../services/AbstractSocket";
4
4
  import { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent, InputComponent } from "../components/gui";
5
5
  import { combineLatest, Subscription } from "rxjs";
6
6
  import { PrebuiltGui, type RpgContext } from "@rpgjs/common";
7
7
 
8
- interface GuiOptions {
8
+ export type GuiRenderer = "canvas" | "vue";
9
+
10
+ export type GuiComponent = ComponentFunction | object;
11
+
12
+ export interface GuiRegistration<
13
+ TData = unknown,
14
+ TComponent extends GuiComponent = GuiComponent,
15
+ > {
9
16
  name?: string;
10
17
  id?: string;
11
- component?: any;
18
+ component: TComponent;
19
+ /**
20
+ * Renderer responsible for this component. CanvasEngine is the v5 default.
21
+ * `@rpgjs/vue` registers the official DOM overlay renderer.
22
+ */
23
+ renderer?: GuiRenderer;
12
24
  display?: boolean;
13
- data?: any;
25
+ data?: TData;
14
26
  /**
15
27
  * Auto display the GUI when added to the system
16
28
  * @default false
@@ -21,7 +33,7 @@ interface GuiOptions {
21
33
  * The GUI will only display when all dependencies are resolved (!= undefined)
22
34
  * @returns Array of Signal dependencies
23
35
  */
24
- dependencies?: () => Signal[];
36
+ dependencies?: () => Signal<unknown>[];
25
37
  /**
26
38
  * Attach the GUI to sprites instead of displaying globally
27
39
  * When true, the GUI will be rendered in character.ce for each sprite
@@ -34,30 +46,42 @@ interface GuiOptions {
34
46
  rpgAttachToSprite?: boolean;
35
47
  }
36
48
 
37
- export interface GuiInstance {
49
+ export type GuiEntry<TData = unknown> =
50
+ | GuiRegistration<TData>
51
+ | GuiComponent;
52
+
53
+ export interface GuiInstance<
54
+ TData = unknown,
55
+ TComponent extends GuiComponent = GuiComponent,
56
+ > {
38
57
  name: string;
39
- component: any;
58
+ component: TComponent;
59
+ renderer: GuiRenderer;
40
60
  display: WritableSignal<boolean>;
41
- data: WritableSignal<any>;
61
+ data: WritableSignal<TData>;
42
62
  openId?: string;
43
63
  autoDisplay: boolean;
44
- dependencies?: Signal[];
64
+ dependencies?: Signal<unknown>[];
45
65
  subscription?: Subscription;
46
66
  attachToSprite?: boolean;
47
67
  }
48
68
 
49
- type GuiState = {
69
+ export type GuiRenderState<
70
+ TData = unknown,
71
+ TComponent extends GuiComponent = GuiComponent,
72
+ > = {
50
73
  name: string;
51
- component: any;
74
+ component: TComponent;
75
+ renderer: GuiRenderer;
52
76
  display: boolean;
53
- data: any;
77
+ data: TData;
54
78
  openId?: string;
55
79
  attachToSprite: boolean;
56
80
  };
57
81
 
58
82
  type VueGuiBridge = {
59
- updateGuiState?: (state: GuiState) => void;
60
- initializeGuiStates?: (states: GuiState[]) => void;
83
+ updateGuiState?: (state: GuiRenderState) => void;
84
+ initializeGuiStates?: (states: GuiRenderState[]) => void;
61
85
  };
62
86
 
63
87
  interface GuiAction {
@@ -150,35 +174,43 @@ export class RpgGui {
150
174
  this.add({
151
175
  name: "rpg-dialog",
152
176
  component: DialogboxComponent,
177
+ renderer: "canvas",
153
178
  });
154
179
  this.add({
155
180
  name: PrebuiltGui.MainMenu,
156
181
  component: MainMenuComponent,
182
+ renderer: "canvas",
157
183
  });
158
184
  this.add({
159
185
  name: PrebuiltGui.Shop,
160
186
  component: ShopComponent,
187
+ renderer: "canvas",
161
188
  });
162
189
  this.add({
163
190
  name: PrebuiltGui.Notification,
164
191
  component: NotificationComponent,
192
+ renderer: "canvas",
165
193
  autoDisplay: true,
166
194
  });
167
195
  this.add({
168
196
  name: PrebuiltGui.Save,
169
197
  component: SaveLoadComponent,
198
+ renderer: "canvas",
170
199
  });
171
200
  this.add({
172
201
  name: PrebuiltGui.TitleScreen,
173
202
  component: TitleScreenComponent,
203
+ renderer: "canvas",
174
204
  });
175
205
  this.add({
176
206
  name: PrebuiltGui.Gameover,
177
207
  component: GameoverComponent,
208
+ renderer: "canvas",
178
209
  });
179
210
  this.add({
180
211
  name: PrebuiltGui.Input,
181
212
  component: InputComponent,
213
+ renderer: "canvas",
182
214
  });
183
215
 
184
216
  this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);
@@ -311,21 +343,24 @@ export class RpgGui {
311
343
  * });
312
344
  * ```
313
345
  */
314
- add(gui: GuiOptions | any) {
315
- const component = this.resolveComponent(gui);
316
- const guiId = this.resolveGuiId(gui, component);
346
+ add(gui: GuiEntry) {
347
+ const registration = this.resolveRegistration(gui);
348
+ const component = registration.component;
349
+ const guiId = registration.name || registration.id || this.resolveComponentName(component);
317
350
  if (!guiId) {
318
351
  throw new Error("GUI must have a name or id");
319
352
  }
320
- const attachToSprite = this.resolveAttachToSprite(gui, component);
353
+ const attachToSprite = this.resolveAttachToSprite(registration, component);
354
+ const renderer = registration.renderer ?? this.resolveLegacyRenderer(component);
321
355
  const guiInstance: GuiInstance = {
322
356
  name: guiId,
323
357
  component,
324
- display: signal<boolean>(gui.display || false),
325
- data: signal<any>(gui.data || {}),
358
+ renderer,
359
+ display: signal<boolean>(registration.display || false),
360
+ data: signal(registration.data ?? {}),
326
361
  openId: undefined,
327
- autoDisplay: gui.autoDisplay || false,
328
- dependencies: gui.dependencies ? gui.dependencies() : [],
362
+ autoDisplay: registration.autoDisplay || false,
363
+ dependencies: registration.dependencies ? registration.dependencies() : [],
329
364
  attachToSprite,
330
365
  };
331
366
 
@@ -342,7 +377,7 @@ export class RpgGui {
342
377
  this._initializeVueComponents();
343
378
 
344
379
  if (guiInstance.autoDisplay) {
345
- this.display(guiId, gui.data);
380
+ this.display(guiId, registration.data);
346
381
  } else {
347
382
  this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());
348
383
  }
@@ -354,8 +389,8 @@ export class RpgGui {
354
389
  this._initializeVueComponents();
355
390
 
356
391
  // Auto display if enabled and it's a CanvasEngine component
357
- if (guiInstance.autoDisplay && typeof gui.component === 'function') {
358
- this.display(guiId, gui.data);
392
+ if (guiInstance.autoDisplay && typeof component === 'function') {
393
+ this.display(guiId, registration.data);
359
394
  }
360
395
  }
361
396
 
@@ -400,15 +435,15 @@ export class RpgGui {
400
435
  return this.attachedGuiDisplayState()[playerId] === true;
401
436
  }
402
437
 
403
- get(id: string): GuiInstance | undefined {
438
+ get<TData = unknown>(id: string): GuiInstance<TData> | undefined {
404
439
  // Check CanvasEngine GUIs first
405
440
  const canvasGui = this.gui()[id];
406
441
  if (canvasGui) {
407
- return canvasGui;
442
+ return canvasGui as GuiInstance<TData>;
408
443
  }
409
444
 
410
445
  // Check Vue GUIs in extraGuis
411
- return this.extraGuis.find(gui => gui.name === id);
446
+ return this.extraGuis.find(gui => gui.name === id) as GuiInstance<TData> | undefined;
412
447
  }
413
448
 
414
449
  exists(id: string): boolean {
@@ -447,7 +482,12 @@ export class RpgGui {
447
482
  * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);
448
483
  * ```
449
484
  */
450
- display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {
485
+ display(
486
+ id: string,
487
+ data: unknown = {},
488
+ dependencies: Signal<unknown>[] = [],
489
+ openId?: string,
490
+ ) {
451
491
  if (!this.exists(id)) {
452
492
  throw throwError(id);
453
493
  }
@@ -595,7 +635,7 @@ export class RpgGui {
595
635
  }
596
636
 
597
637
  private isVueComponentInstance(gui: GuiInstance) {
598
- return typeof gui.component !== "function";
638
+ return gui.renderer === "vue";
599
639
  }
600
640
 
601
641
  private removeCanvasGui(guiId: string) {
@@ -614,22 +654,46 @@ export class RpgGui {
614
654
  }
615
655
  }
616
656
 
617
- private resolveComponent(gui: GuiOptions | any) {
618
- return gui?.component ?? gui;
657
+ private resolveRegistration(gui: GuiEntry): GuiRegistration {
658
+ if (
659
+ typeof gui === "object"
660
+ && gui !== null
661
+ && "component" in gui
662
+ ) {
663
+ return gui as GuiRegistration;
664
+ }
665
+ return { component: gui };
666
+ }
667
+
668
+ private resolveComponentName(component: GuiComponent): string | undefined {
669
+ const named = component as { name?: unknown; __name?: unknown };
670
+ if (typeof named.name === "string" && named.name.length > 0) return named.name;
671
+ if (typeof named.__name === "string" && named.__name.length > 0) return named.__name;
672
+ return undefined;
619
673
  }
620
674
 
621
- private resolveGuiId(gui: GuiOptions | any, component: any) {
622
- return gui?.name || gui?.id || component?.name || component?.__name;
675
+ private resolveLegacyRenderer(component: GuiComponent): GuiRenderer {
676
+ return typeof component === "function" ? "canvas" : "vue";
623
677
  }
624
678
 
625
- private resolveAttachToSprite(gui: GuiOptions | any, component: any) {
626
- return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);
679
+ private resolveAttachToSprite(gui: GuiRegistration, component: GuiComponent) {
680
+ const componentOptions = component as {
681
+ attachToSprite?: unknown;
682
+ rpgAttachToSprite?: unknown;
683
+ };
684
+ return !!(
685
+ gui.attachToSprite
686
+ || gui.rpgAttachToSprite
687
+ || componentOptions.attachToSprite
688
+ || componentOptions.rpgAttachToSprite
689
+ );
627
690
  }
628
691
 
629
- private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {
692
+ private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiRenderState {
630
693
  return {
631
694
  name: gui.name,
632
695
  component: gui.component,
696
+ renderer: gui.renderer,
633
697
  display,
634
698
  data,
635
699
  openId: gui.openId,
package/src/RpgClient.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  RpgInteractionBehavior,
14
14
  RpgInteractionMatcher,
15
15
  } from './services/interactions'
16
+ import type { GuiEntry } from './Gui/Gui'
16
17
 
17
18
  type RpgClass<T = any> = new (...args: any[]) => T
18
19
  type RpgComponent = RpgClientObject
@@ -570,69 +571,7 @@ export interface RpgClient {
570
571
  * @prop {Array<GuiOptions>} [gui]
571
572
  * @memberof RpgClient
572
573
  * */
573
- gui?: ({
574
- id: string,
575
- component: ComponentFunction,
576
- /**
577
- * Auto display the GUI when added to the system
578
- * @default false
579
- */
580
- autoDisplay?: boolean,
581
- /**
582
- * Function that returns an array of Signal dependencies
583
- * The GUI will only display when all dependencies are resolved (!= undefined)
584
- */
585
- dependencies?: () => Signal[]
586
- /**
587
- * Attach the GUI to sprites instead of displaying globally
588
- *
589
- * When set to `true`, the GUI component will be rendered directly on each sprite
590
- * in the game world, rather than being displayed as a fixed overlay on the screen.
591
- * This is useful for tooltips, health bars, name tags, or any UI element that
592
- * should follow a specific sprite.
593
- *
594
- * The GUI will be rendered in the `character.ce` component for each sprite.
595
- * You can control the display state of attached GUIs from the server side using
596
- * `player.showAttachedGui()` and `player.hideAttachedGui()` methods.
597
- *
598
- * @default false
599
- * @example
600
- * ```ts
601
- * import { defineModule, RpgClient } from '@rpgjs/client'
602
- * import TooltipComponent from './tooltip.ce'
603
- *
604
- * defineModule<RpgClient>({
605
- * gui: [
606
- * {
607
- * id: "my-tooltip",
608
- * component: TooltipComponent,
609
- * attachToSprite: true
610
- * }
611
- * ]
612
- * })
613
- * ```
614
- *
615
- * On the server side, control the display:
616
- * ```ts
617
- * // Show the tooltip for this player
618
- * player.showAttachedGui()
619
- *
620
- * // Hide the tooltip for this player
621
- * player.hideAttachedGui()
622
- *
623
- * // Show tooltip for other players
624
- * player.showAttachedGui([otherPlayer1, otherPlayer2])
625
- * ```
626
- */
627
- attachToSprite?: boolean
628
- /**
629
- * Vue v4 compatibility alias for `attachToSprite`.
630
- *
631
- * Prefer `attachToSprite` in v5 projects. This is read by `@rpgjs/vue`
632
- * for Vue GUI components migrated from the v4 GUI API.
633
- */
634
- rpgAttachToSprite?: boolean
635
- } | any)[],
574
+ gui?: GuiEntry[],
636
575
 
637
576
  /**
638
577
  * Array containing the list of sounds
@@ -1,6 +1,11 @@
1
1
  import { describe, expectTypeOf, test } from "vitest";
2
2
  import type { RpgContext, RpgProvider } from "@rpgjs/common";
3
- import { provideMmorpg, startGame } from "./index";
3
+ import {
4
+ provideMmorpg,
5
+ startGame,
6
+ type GuiRegistration,
7
+ type GuiRenderer,
8
+ } from "./index";
4
9
 
5
10
  describe("client public API types", () => {
6
11
  test("bootstrap and providers expose RPGJS-owned contracts", () => {
@@ -16,4 +21,17 @@ describe("client public API types", () => {
16
21
  type LegacyContext = typeof import("./index")["Context"];
17
22
  expectTypeOf<LegacyContext>();
18
23
  });
24
+
25
+ test("GUI registrations expose renderer-neutral typed data", () => {
26
+ type DialogData = { message: string };
27
+ const registration: GuiRegistration<DialogData> = {
28
+ id: "dialog",
29
+ component: () => null,
30
+ renderer: "canvas",
31
+ data: { message: "Hello" },
32
+ };
33
+
34
+ expectTypeOf(registration.renderer).toEqualTypeOf<GuiRenderer | undefined>();
35
+ expectTypeOf(registration.data).toEqualTypeOf<DialogData | undefined>();
36
+ });
19
37
  });