@rpgjs/client 5.0.0-beta.24 → 5.0.0-beta.25

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @rpgjs/client
2
2
 
3
+ ## 5.0.0-beta.25
4
+
5
+ ### Minor Changes
6
+
7
+ - 0512640: Add reusable typed input and textarea controls for standalone forms and dialog boxes, with shared server-side validation and an RPGJS Studio block that stores the submitted value.
8
+
9
+ ### Patch Changes
10
+
11
+ - ccb9495: Fix TypeScript declaration errors across the package build, align multi-target declaration exports, complete movement API overloads, and make package and root builds fail when declaration generation reports a type error.
12
+ - Updated dependencies [ccb9495]
13
+ - Updated dependencies [f6aa046]
14
+ - Updated dependencies [0512640]
15
+ - @rpgjs/common@5.0.0-beta.24
16
+ - @rpgjs/server@5.0.0-beta.25
17
+ - @rpgjs/ui-css@5.0.0-beta.23
18
+
3
19
  ## 5.0.0-beta.24
4
20
 
5
21
  ### Patch Changes
package/dist/Gui/Gui.js CHANGED
@@ -7,6 +7,7 @@ import __ce_component$3 from "../components/gui/menu/main-menu.ce.js";
7
7
  import __ce_component$4 from "../components/gui/notification/notification.ce.js";
8
8
  import __ce_component$5 from "../components/gui/title-screen.ce.js";
9
9
  import __ce_component$6 from "../components/gui/gameover.ce.js";
10
+ import __ce_component$7 from "../components/gui/input.ce.js";
10
11
  import "../components/gui/index.js";
11
12
  import { signal } from "canvasengine";
12
13
  import { PrebuiltGui } from "@rpgjs/common";
@@ -113,6 +114,10 @@ var RpgGui = class {
113
114
  name: PrebuiltGui.Gameover,
114
115
  component: __ce_component$6
115
116
  });
117
+ this.add({
118
+ name: PrebuiltGui.Input,
119
+ component: __ce_component$7
120
+ });
116
121
  this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);
117
122
  }
118
123
  async _initialize() {
@@ -1 +1 @@
1
- {"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n openId?: string;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n openId?: string;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any; guiOpenId?: string }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data, [], data.guiOpenId);\n });\n\n this.webSocket.on(\"gui.exit\", (payload: string | { guiId: string; guiOpenId?: string }) => {\n const guiId = typeof payload === \"string\" ? payload : payload.guiId;\n const guiOpenId = typeof payload === \"string\" ? undefined : payload.guiOpenId;\n const current = this.get(guiId);\n if (guiOpenId && current?.openId && current.openId !== guiOpenId) {\n return;\n }\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any, guiOpenId?: unknown) {\n const normalizedOpenId =\n typeof guiOpenId === \"string\" && guiOpenId.length > 0 ? guiOpenId : undefined;\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n guiOpenId: normalizedOpenId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n openId: undefined,\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n const show = () => {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n if (isVueComponent) {\n this._notifyVueGui(id, true, data);\n }\n };\n\n const deps = dependencies.length > 0\n ? dependencies\n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n show();\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n show();\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance, openId?: string) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n guiInstance.openId = undefined;\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n openId: gui.openId,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent, InputComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n openId?: string;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n openId?: string;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n this.add({\n name: PrebuiltGui.Input,\n component: InputComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any; guiOpenId?: string }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data, [], data.guiOpenId);\n });\n\n this.webSocket.on(\"gui.exit\", (payload: string | { guiId: string; guiOpenId?: string }) => {\n const guiId = typeof payload === \"string\" ? payload : payload.guiId;\n const guiOpenId = typeof payload === \"string\" ? undefined : payload.guiOpenId;\n const current = this.get(guiId);\n if (guiOpenId && current?.openId && current.openId !== guiOpenId) {\n return;\n }\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any, guiOpenId?: unknown) {\n const normalizedOpenId =\n typeof guiOpenId === \"string\" && guiOpenId.length > 0 ? guiOpenId : undefined;\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n guiOpenId: normalizedOpenId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n openId: undefined,\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n const show = () => {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n if (isVueComponent) {\n this._notifyVueGui(id, true, data);\n }\n };\n\n const deps = dependencies.length > 0\n ? dependencies\n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n show();\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n show();\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance, openId?: string) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n guiInstance.openId = undefined;\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n openId: gui.openId,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { inject } from "../../../core/inject.js";
2
2
  import { getKeyboardControlBind } from "../../../services/actionInput.js";
3
+ import __ce_component$1 from "../input-field.ce.js";
3
4
  import { RpgClientEngine } from "../../../RpgClientEngine.js";
4
5
  import { DOMContainer, DOMElement, DOMSprite, Navigation, computed, cond, createTabindexNavigator, effect, h, loop, mount, signal, useDefineEmits, useDefineProps, useProps } from "canvasengine";
5
6
  import { delay } from "@rpgjs/common";
@@ -23,6 +24,8 @@ function component($$props) {
23
24
  const position = computed(() => dialogData().position);
24
25
  const typewriterEffect = computed(() => dialogData().typewriterEffect);
25
26
  const fullWidth = computed(() => dialogData().fullWidth || false);
27
+ const dialogInput = computed(() => dialogData().input || null);
28
+ const hasInput = computed(() => !!dialogInput());
26
29
  const resolveProp = (value) => typeof value === "function" ? value() : value;
27
30
  const normalizeOpenId = (value) => {
28
31
  const resolved = resolveProp(value);
@@ -76,7 +79,7 @@ function component($$props) {
76
79
  startTypewriter(text);
77
80
  });
78
81
  const hasChoices = computed(() => dialogChoices().length > 0);
79
- const showIndicator = computed(() => !hasChoices() && !isTyping());
82
+ const showIndicator = computed(() => !hasChoices() && !hasInput() && !isTyping());
80
83
  const nav = createTabindexNavigator(selectedItem, { count: () => dialogChoices().length }, "wrap");
81
84
  function selectChoice(index) {
82
85
  return function() {
@@ -91,6 +94,10 @@ function component($$props) {
91
94
  event?.stopPropagation?.();
92
95
  };
93
96
  const closeDialog = () => {
97
+ if (hasInput()) {
98
+ onInteraction?.("cancel");
99
+ return;
100
+ }
94
101
  _onFinish();
95
102
  };
96
103
  const closeDialogFromPointer = (event) => {
@@ -100,6 +107,12 @@ function component($$props) {
100
107
  const onSelect = (index) => {
101
108
  _onFinish(index);
102
109
  };
110
+ const submitInput = (payload) => {
111
+ onInteraction?.("submit", payload);
112
+ };
113
+ const cancelInput = () => {
114
+ onInteraction?.("cancel");
115
+ };
103
116
  const canAcceptAction = () => Date.now() - openedAt >= ACTION_OPEN_GUARD_MS;
104
117
  const controls = signal({
105
118
  up: {
@@ -142,6 +155,7 @@ function component($$props) {
142
155
  finishTyping();
143
156
  return;
144
157
  }
158
+ if (hasInput()) return;
145
159
  if (hasChoices()) return;
146
160
  _onFinish();
147
161
  }
@@ -223,7 +237,14 @@ function component($$props) {
223
237
  click: selectChoice(index)
224
238
  },
225
239
  textContent: choice.text
226
- })))))
240
+ }))))),
241
+ cond(computed(() => hasInput()), () => h(__ce_component$1, {
242
+ data: dialogInput,
243
+ layout: "dialog",
244
+ showMessage: false,
245
+ submit: submitInput,
246
+ cancel: cancelInput
247
+ }))
227
248
  ]), cond(computed(() => hasFace()), () => h(DOMElement, {
228
249
  element: "div",
229
250
  attrs: { class: "rpg-ui-dialog-face" }
@@ -1 +1 @@
1
- {"version":3,"file":"index.ce.js","names":[],"sources":["../../../../src/components/gui/dialogbox/index.ce"],"sourcesContent":["<DOMContainer width=\"100%\" height=\"100%\" controls={dialogControls}>\n <div class=\"rpg-ui-dialog-layer\" click={closeDialogFromPointer}>\n <button class=\"rpg-ui-close-button\" type=\"button\" title=\"Close\" aria-label=\"Close dialog\" click={closeDialogFromPointer}>x</button>\n <div\n class=\"rpg-ui-dialog-container\"\n data-position={dialogPosition()}\n data-full-width={isFullWidth() ? \"true\" : \"false\"}\n data-has-face={hasFace() ? \"true\" : \"false\"}\n >\n <div class=\"rpg-ui-dialog rpg-anim-fade-in\" click={stopEvent}>\n <div class=\"rpg-ui-dialog-body\">\n <div>\n @if (speakerName()) {\n <div class=\"rpg-ui-dialog-speaker\">{speakerName()}</div>\n }\n <div class=\"rpg-ui-dialog-content\">\n {displayMessage()}\n </div>\n @if (hasChoices()) {\n <Navigation tabindex={selectedItem} controls={controls}>\n <div class=\"rpg-ui-dialog-choices\">\n @for ((choice,index) of dialogChoices()) {\n <div\n class=\"rpg-ui-dialog-choice\"\n class={{active: selectedItem() === index}}\n tabindex={index}\n data-choice-index={index}\n click={selectChoice(index)}\n >{{ choice.text }}</div>\n }\n </div>\n </Navigation>\n }\n </div>\n @if (hasFace()) {\n <div class=\"rpg-ui-dialog-face\">\n <DOMSprite\n sheet={faceSheet(dialogFace())}\n width=\"100%\"\n height=\"100%\"\n objectFit=\"contain\"\n />\n </div>\n }\n </div>\n @if (showIndicator) {\n <div class=\"rpg-ui-dialog-indicator\"></div>\n }\n </div>\n </div>\n </div>\n</DOMContainer>\n \n<script>\n import { effect, signal, computed, createTabindexNavigator, mount } from \"canvasengine\";\n import { inject } from \"../../../core/inject\";\n import { RpgClientEngine } from \"../../../RpgClientEngine\";\n import { delay } from \"@rpgjs/common\";\n import { getKeyboardControlBind } from \"../../../services/actionInput\";\n\n const engine = inject(RpgClientEngine);\n const currentPlayer = engine.scene.currentPlayer\n const keyboardControls = engine.globalConfig.keyboardControls;\n\n engine.stopProcessingInput = true;\n\n const selectedItem = signal(0)\n const ACTION_OPEN_GUARD_MS = 150;\n const openedAt = Date.now();\n\n const {\n data,\n onFinish,\n onInteraction,\n guiOpenId\n } = defineProps();\n\n const dialogData = computed(() => data() || {});\n const dialogChoices = computed(() => Array.isArray(dialogData().choices) ? dialogData().choices : []);\n const message = computed(() => dialogData().message);\n const speaker = computed(() => dialogData().speaker);\n const position = computed(() => dialogData().position);\n const typewriterEffect = computed(() => dialogData().typewriterEffect);\n const fullWidth = computed(() => dialogData().fullWidth || false);\n\n const resolveProp = (value) => typeof value === \"function\" ? value() : value;\n const normalizeOpenId = (value) => {\n const resolved = resolveProp(value);\n return typeof resolved === \"string\" && resolved.length > 0 ? resolved : undefined;\n };\n\n const speakerName = computed(() => {\n const value = resolveProp(speaker());\n return value ? String(value) : \"\";\n });\n\n const dialogPosition = computed(() => resolveProp(position()) || \"bottom\");\n const isFullWidth = computed(() => resolveProp(fullWidth) !== false);\n const dialogFace = computed(() => resolveProp(dialogData().face));\n const hasFace = computed(() => {\n const value = dialogFace();\n return !!(value && value.id);\n });\n\n const displayMessage = signal(\"\");\n const fullMessage = signal(\"\");\n const isTyping = signal(false);\n let typewriterTimer = null;\n let rootElement = null;\n\n mount((element) => {\n rootElement = element;\n });\n\n const startTypewriter = (text) => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n displayMessage.set(\"\");\n if (!text) return;\n let index = 0;\n isTyping.set(true);\n typewriterTimer = setInterval(() => {\n index += 1;\n displayMessage.set(text.slice(0, index));\n if (index >= text.length) {\n clearInterval(typewriterTimer);\n typewriterTimer = null;\n isTyping.set(false);\n }\n }, 20);\n };\n\n const finishTyping = () => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n typewriterTimer = null;\n displayMessage.set(fullMessage());\n isTyping.set(false);\n };\n\n effect(() => {\n const text = resolveProp(message()) || \"\";\n fullMessage.set(text);\n const useTypewriter = resolveProp(typewriterEffect()) !== false;\n if (!useTypewriter) {\n finishTyping();\n return;\n }\n startTypewriter(text);\n });\n\n\n const hasChoices = computed(() => dialogChoices().length > 0);\n const showIndicator = computed(() => !hasChoices() && !isTyping());\n const nav = createTabindexNavigator(selectedItem, { count: () => dialogChoices().length }, 'wrap');\n\n function selectChoice(index) {\n return function() {\n selectedItem.set(index);\n onSelect(index);\n }\n }\n\n function _onFinish(value) {\n if (onFinish) onFinish(value, normalizeOpenId(guiOpenId));\n }\n\n const stopEvent = (event) => {\n event?.stopPropagation?.();\n };\n\n const closeDialog = () => {\n _onFinish();\n };\n\n const closeDialogFromPointer = (event) => {\n stopEvent(event);\n closeDialog();\n };\n\n const onSelect = (index) => {\n _onFinish(index);\n };\n\n const canAcceptAction = () => Date.now() - openedAt >= ACTION_OPEN_GUARD_MS;\n\n const controls = signal({\n up: {\n repeat: true,\n bind: keyboardControls.up,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(-1);\n }\n },\n down: {\n repeat: true,\n bind: keyboardControls.down,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(1);\n }\n },\n action: {\n bind: getKeyboardControlBind(keyboardControls.action),\n keyDown() {\n if (!canAcceptAction()) {\n return;\n }\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (!hasChoices()) return;\n onSelect(selectedItem());\n }\n },\n gamepad: {\n enabled: true\n }\n });\n \n const dialogControls = signal({\n action: {\n bind: getKeyboardControlBind(keyboardControls.action),\n keyDown() {\n if (!canAcceptAction()) {\n return;\n }\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (hasChoices()) return;\n _onFinish();\n }\n },\n })\n\n const faceSheet = (faceValue) => {\n if (!faceValue || !faceValue.id) {\n return undefined;\n }\n return {\n definition: engine.getSpriteSheet(faceValue.id),\n playing: faceValue.expression || \"default\",\n };\n }\n\n mount((element) => {\n return () => {\n // Wait destroy is finished before start processing input\n delay(() => {\n engine.stopProcessingInput = false;\n })\n }\n })\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.ce.js","names":[],"sources":["../../../../src/components/gui/dialogbox/index.ce"],"sourcesContent":["<DOMContainer width=\"100%\" height=\"100%\" controls={dialogControls}>\n <div class=\"rpg-ui-dialog-layer\" click={closeDialogFromPointer}>\n <button class=\"rpg-ui-close-button\" type=\"button\" title=\"Close\" aria-label=\"Close dialog\" click={closeDialogFromPointer}>x</button>\n <div\n class=\"rpg-ui-dialog-container\"\n data-position={dialogPosition()}\n data-full-width={isFullWidth() ? \"true\" : \"false\"}\n data-has-face={hasFace() ? \"true\" : \"false\"}\n >\n <div class=\"rpg-ui-dialog rpg-anim-fade-in\" click={stopEvent}>\n <div class=\"rpg-ui-dialog-body\">\n <div>\n @if (speakerName()) {\n <div class=\"rpg-ui-dialog-speaker\">{speakerName()}</div>\n }\n <div class=\"rpg-ui-dialog-content\">\n {displayMessage()}\n </div>\n @if (hasChoices()) {\n <Navigation tabindex={selectedItem} controls={controls}>\n <div class=\"rpg-ui-dialog-choices\">\n @for ((choice,index) of dialogChoices()) {\n <div\n class=\"rpg-ui-dialog-choice\"\n class={{active: selectedItem() === index}}\n tabindex={index}\n data-choice-index={index}\n click={selectChoice(index)}\n >{{ choice.text }}</div>\n }\n </div>\n </Navigation>\n }\n @if (hasInput()) {\n <InputField\n data={dialogInput}\n layout=\"dialog\"\n showMessage={false}\n submit={submitInput}\n cancel={cancelInput}\n />\n }\n </div>\n @if (hasFace()) {\n <div class=\"rpg-ui-dialog-face\">\n <DOMSprite\n sheet={faceSheet(dialogFace())}\n width=\"100%\"\n height=\"100%\"\n objectFit=\"contain\"\n />\n </div>\n }\n </div>\n @if (showIndicator) {\n <div class=\"rpg-ui-dialog-indicator\"></div>\n }\n </div>\n </div>\n </div>\n</DOMContainer>\n \n<script>\n import { effect, signal, computed, createTabindexNavigator, mount } from \"canvasengine\";\n import { inject } from \"../../../core/inject\";\n import { RpgClientEngine } from \"../../../RpgClientEngine\";\n import { delay } from \"@rpgjs/common\";\n import { getKeyboardControlBind } from \"../../../services/actionInput\";\n import InputField from \"../input-field.ce\";\n\n const engine = inject(RpgClientEngine);\n const currentPlayer = engine.scene.currentPlayer\n const keyboardControls = engine.globalConfig.keyboardControls;\n\n engine.stopProcessingInput = true;\n\n const selectedItem = signal(0)\n const ACTION_OPEN_GUARD_MS = 150;\n const openedAt = Date.now();\n\n const {\n data,\n onFinish,\n onInteraction,\n guiOpenId\n } = defineProps();\n\n const dialogData = computed(() => data() || {});\n const dialogChoices = computed(() => Array.isArray(dialogData().choices) ? dialogData().choices : []);\n const message = computed(() => dialogData().message);\n const speaker = computed(() => dialogData().speaker);\n const position = computed(() => dialogData().position);\n const typewriterEffect = computed(() => dialogData().typewriterEffect);\n const fullWidth = computed(() => dialogData().fullWidth || false);\n const dialogInput = computed(() => dialogData().input || null);\n const hasInput = computed(() => !!dialogInput());\n\n const resolveProp = (value) => typeof value === \"function\" ? value() : value;\n const normalizeOpenId = (value) => {\n const resolved = resolveProp(value);\n return typeof resolved === \"string\" && resolved.length > 0 ? resolved : undefined;\n };\n\n const speakerName = computed(() => {\n const value = resolveProp(speaker());\n return value ? String(value) : \"\";\n });\n\n const dialogPosition = computed(() => resolveProp(position()) || \"bottom\");\n const isFullWidth = computed(() => resolveProp(fullWidth) !== false);\n const dialogFace = computed(() => resolveProp(dialogData().face));\n const hasFace = computed(() => {\n const value = dialogFace();\n return !!(value && value.id);\n });\n\n const displayMessage = signal(\"\");\n const fullMessage = signal(\"\");\n const isTyping = signal(false);\n let typewriterTimer = null;\n let rootElement = null;\n\n mount((element) => {\n rootElement = element;\n });\n\n const startTypewriter = (text) => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n displayMessage.set(\"\");\n if (!text) return;\n let index = 0;\n isTyping.set(true);\n typewriterTimer = setInterval(() => {\n index += 1;\n displayMessage.set(text.slice(0, index));\n if (index >= text.length) {\n clearInterval(typewriterTimer);\n typewriterTimer = null;\n isTyping.set(false);\n }\n }, 20);\n };\n\n const finishTyping = () => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n typewriterTimer = null;\n displayMessage.set(fullMessage());\n isTyping.set(false);\n };\n\n effect(() => {\n const text = resolveProp(message()) || \"\";\n fullMessage.set(text);\n const useTypewriter = resolveProp(typewriterEffect()) !== false;\n if (!useTypewriter) {\n finishTyping();\n return;\n }\n startTypewriter(text);\n });\n\n\n const hasChoices = computed(() => dialogChoices().length > 0);\n const showIndicator = computed(() => !hasChoices() && !hasInput() && !isTyping());\n const nav = createTabindexNavigator(selectedItem, { count: () => dialogChoices().length }, 'wrap');\n\n function selectChoice(index) {\n return function() {\n selectedItem.set(index);\n onSelect(index);\n }\n }\n\n function _onFinish(value) {\n if (onFinish) onFinish(value, normalizeOpenId(guiOpenId));\n }\n\n const stopEvent = (event) => {\n event?.stopPropagation?.();\n };\n\n const closeDialog = () => {\n if (hasInput()) {\n onInteraction?.(\"cancel\");\n return;\n }\n _onFinish();\n };\n\n const closeDialogFromPointer = (event) => {\n stopEvent(event);\n closeDialog();\n };\n\n const onSelect = (index) => {\n _onFinish(index);\n };\n\n const submitInput = (payload) => {\n onInteraction?.(\"submit\", payload);\n };\n\n const cancelInput = () => {\n onInteraction?.(\"cancel\");\n };\n\n const canAcceptAction = () => Date.now() - openedAt >= ACTION_OPEN_GUARD_MS;\n\n const controls = signal({\n up: {\n repeat: true,\n bind: keyboardControls.up,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(-1);\n }\n },\n down: {\n repeat: true,\n bind: keyboardControls.down,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(1);\n }\n },\n action: {\n bind: getKeyboardControlBind(keyboardControls.action),\n keyDown() {\n if (!canAcceptAction()) {\n return;\n }\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (!hasChoices()) return;\n onSelect(selectedItem());\n }\n },\n gamepad: {\n enabled: true\n }\n });\n \n const dialogControls = signal({\n action: {\n bind: getKeyboardControlBind(keyboardControls.action),\n keyDown() {\n if (!canAcceptAction()) {\n return;\n }\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (hasInput()) return;\n if (hasChoices()) return;\n _onFinish();\n }\n },\n })\n\n const faceSheet = (faceValue) => {\n if (!faceValue || !faceValue.id) {\n return undefined;\n }\n return {\n definition: engine.getSpriteSheet(faceValue.id),\n playing: faceValue.expression || \"default\",\n };\n }\n\n mount((element) => {\n return () => {\n // Wait destroy is finished before start processing input\n delay(() => {\n engine.stopProcessingInput = false;\n })\n }\n })\n</script>\n"],"mappings":"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"}
@@ -12,4 +12,6 @@ import { default as ExitMenuComponent } from './menu/exit-menu.ce';
12
12
  import { default as NotificationComponent } from './notification/notification.ce';
13
13
  import { default as TitleScreenComponent } from './title-screen.ce';
14
14
  import { default as GameoverComponent } from './gameover.ce';
15
- export { DialogboxComponent, BoxComponent, ShopComponent, HudComponent, SaveLoadComponent, MainMenuComponent, ItemsMenuComponent, SkillsMenuComponent, EquipMenuComponent, OptionsMenuComponent, ExitMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent };
15
+ import { default as InputComponent } from './input.ce';
16
+ import { default as InputFieldComponent } from './input-field.ce';
17
+ export { DialogboxComponent, BoxComponent, ShopComponent, HudComponent, SaveLoadComponent, MainMenuComponent, ItemsMenuComponent, SkillsMenuComponent, EquipMenuComponent, OptionsMenuComponent, ExitMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent, InputComponent, InputFieldComponent };
@@ -1,3 +1,4 @@
1
+ import "./input-field.ce.js";
1
2
  import "./dialogbox/index.ce.js";
2
3
  import "./box.ce.js";
3
4
  import "./shop/shop.ce.js";
@@ -12,3 +13,4 @@ import "./menu/main-menu.ce.js";
12
13
  import "./notification/notification.ce.js";
13
14
  import "./title-screen.ce.js";
14
15
  import "./gameover.ce.js";
16
+ import "./input.ce.js";
@@ -0,0 +1,123 @@
1
+ import { inject } from "../../core/inject.js";
2
+ import { RpgClientEngine } from "../../RpgClientEngine.js";
3
+ import { DOMElement, computed, cond, effect, h, signal, useDefineEmits, useDefineProps, useProps } from "canvasengine";
4
+ //#region src/components/gui/input-field.ce
5
+ function component($$props) {
6
+ useProps($$props);
7
+ const defineProps = useDefineProps($$props);
8
+ const defineEmits = useDefineEmits($$props);
9
+ const { t } = inject(RpgClientEngine).i18n();
10
+ const { data, layout, showMessage } = defineProps({
11
+ layout: "standalone",
12
+ showMessage: true
13
+ });
14
+ const { submit, cancel } = defineEmits();
15
+ const formData = computed(() => data() || {});
16
+ const value = signal(String(formData().defaultValue ?? ""));
17
+ const fieldId = computed(() => `rpg-input-value-${layout()}`);
18
+ const errorText = computed(() => formData().errorKey ? t(formData().errorKey, formData().errorParams || {}) : "");
19
+ const showCancelButton = computed(() => formData().cancelButton !== false);
20
+ let focused = false;
21
+ function submitForm(event, collectedData) {
22
+ const submittedValue = collectedData && Object.prototype.hasOwnProperty.call(collectedData, "value") ? collectedData.value : event?.currentTarget?.elements?.value?.value ?? value();
23
+ value.set(String(submittedValue ?? ""));
24
+ submit({ value: submittedValue ?? "" });
25
+ }
26
+ function cancelForm() {
27
+ cancel();
28
+ }
29
+ function handleKeydown(event) {
30
+ if (event?.key !== "Escape") return;
31
+ event.preventDefault?.();
32
+ event.stopPropagation?.();
33
+ cancelForm();
34
+ }
35
+ effect(() => {
36
+ if (focused) return;
37
+ focused = true;
38
+ setTimeout(() => {
39
+ document.getElementById(fieldId())?.focus?.();
40
+ });
41
+ });
42
+ return h(DOMElement, {
43
+ element: "form",
44
+ attrs: {
45
+ class: "rpg-ui-input-form",
46
+ "data-layout": computed(() => layout()),
47
+ submit: submitForm,
48
+ keydown: handleKeydown
49
+ }
50
+ }, [
51
+ cond(computed(() => showMessage()), () => h(DOMElement, {
52
+ element: "label",
53
+ attrs: {
54
+ class: "rpg-ui-input-message",
55
+ for: computed(() => fieldId())
56
+ },
57
+ textContent: computed(() => formData().message)
58
+ })),
59
+ cond(computed(() => formData().control === "textarea"), () => h(DOMElement, {
60
+ element: "textarea",
61
+ attrs: {
62
+ id: computed(() => fieldId()),
63
+ class: "rpg-ui-input rpg-ui-input-dialog-field",
64
+ name: "value",
65
+ value,
66
+ placeholder: computed(() => formData().placeholder || ""),
67
+ rows: computed(() => formData().rows || 4),
68
+ minlength: computed(() => formData().minLength),
69
+ maxlength: computed(() => formData().maxLength),
70
+ "aria-required": computed(() => formData().required ? "true" : "false")
71
+ }
72
+ }), () => h(DOMElement, {
73
+ element: "input",
74
+ attrs: {
75
+ id: computed(() => fieldId()),
76
+ class: "rpg-ui-input rpg-ui-input-dialog-field",
77
+ name: "value",
78
+ type: computed(() => formData().type || "text"),
79
+ value,
80
+ placeholder: computed(() => formData().placeholder || ""),
81
+ min: computed(() => formData().min),
82
+ max: computed(() => formData().max),
83
+ step: computed(() => formData().step),
84
+ minlength: computed(() => formData().minLength),
85
+ maxlength: computed(() => formData().maxLength),
86
+ "aria-required": computed(() => formData().required ? "true" : "false")
87
+ }
88
+ })),
89
+ cond(computed(() => errorText()), () => h(DOMElement, {
90
+ element: "div",
91
+ attrs: {
92
+ class: "rpg-ui-input-error",
93
+ role: "alert"
94
+ },
95
+ textContent: computed(() => errorText())
96
+ })),
97
+ h(DOMElement, {
98
+ element: "div",
99
+ attrs: { class: "rpg-ui-input-actions" }
100
+ }, [cond(computed(() => showCancelButton()), () => h(DOMElement, {
101
+ element: "button",
102
+ attrs: {
103
+ class: "rpg-ui-btn rpg-ui-input-button rpg-ui-input-button-cancel",
104
+ type: "button",
105
+ click: cancelForm
106
+ },
107
+ textContent: computed(() => formData().cancelText || t("rpg.input.cancel"))
108
+ })), h(DOMElement, {
109
+ element: "button",
110
+ attrs: {
111
+ class: "rpg-ui-btn rpg-ui-input-button rpg-ui-input-button-confirm",
112
+ "data-variant": "primary",
113
+ type: "submit"
114
+ },
115
+ textContent: computed(() => formData().confirmText || t("rpg.input.confirm"))
116
+ })])
117
+ ]);
118
+ }
119
+ var __ce_component = component;
120
+ //#endregion
121
+ export { __ce_component as default };
122
+
123
+ //# sourceMappingURL=input-field.ce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-field.ce.js","names":[],"sources":["../../../src/components/gui/input-field.ce"],"sourcesContent":["<form\n class=\"rpg-ui-input-form\"\n data-layout={layout()}\n submit={submitForm}\n keydown={handleKeydown}\n>\n @if (showMessage()) {\n <label class=\"rpg-ui-input-message\" for={fieldId()}>{formData().message}</label>\n }\n @if (formData().control === \"textarea\") {\n <textarea\n id={fieldId()}\n class=\"rpg-ui-input rpg-ui-input-dialog-field\"\n name=\"value\"\n value={value}\n placeholder={formData().placeholder || \"\"}\n rows={formData().rows || 4}\n minlength={formData().minLength}\n maxlength={formData().maxLength}\n aria-required={formData().required ? \"true\" : \"false\"}\n ></textarea>\n }\n @else {\n <input\n id={fieldId()}\n class=\"rpg-ui-input rpg-ui-input-dialog-field\"\n name=\"value\"\n type={formData().type || \"text\"}\n value={value}\n placeholder={formData().placeholder || \"\"}\n min={formData().min}\n max={formData().max}\n step={formData().step}\n minlength={formData().minLength}\n maxlength={formData().maxLength}\n aria-required={formData().required ? \"true\" : \"false\"}\n />\n }\n @if (errorText()) {\n <div class=\"rpg-ui-input-error\" role=\"alert\">{errorText()}</div>\n }\n <div class=\"rpg-ui-input-actions\">\n @if (showCancelButton()) {\n <button class=\"rpg-ui-btn rpg-ui-input-button rpg-ui-input-button-cancel\" type=\"button\" click={cancelForm}>\n {formData().cancelText || t(\"rpg.input.cancel\")}\n </button>\n }\n <button\n class=\"rpg-ui-btn rpg-ui-input-button rpg-ui-input-button-confirm\"\n data-variant=\"primary\"\n type=\"submit\"\n >\n {formData().confirmText || t(\"rpg.input.confirm\")}\n </button>\n </div>\n</form>\n\n<script>\n import { computed, effect, signal } from \"canvasengine\";\n import { inject } from \"../../core/inject\";\n import { RpgClientEngine } from \"../../RpgClientEngine\";\n\n const engine = inject(RpgClientEngine);\n const { t } = engine.i18n();\n const { data, layout, showMessage } = defineProps({\n layout: \"standalone\",\n showMessage: true,\n });\n const { submit, cancel } = defineEmits();\n const formData = computed(() => data() || {});\n const value = signal(String(formData().defaultValue ?? \"\"));\n const fieldId = computed(() => `rpg-input-value-${layout()}`);\n const errorText = computed(() => formData().errorKey\n ? t(formData().errorKey, formData().errorParams || {})\n : \"\"\n );\n const showCancelButton = computed(() => formData().cancelButton !== false);\n let focused = false;\n\n function submitForm(event, collectedData) {\n const submittedValue = collectedData && Object.prototype.hasOwnProperty.call(collectedData, \"value\")\n ? collectedData.value\n : event?.currentTarget?.elements?.value?.value ?? value();\n value.set(String(submittedValue ?? \"\"));\n submit({ value: submittedValue ?? \"\" });\n }\n\n function cancelForm() {\n cancel();\n }\n\n function handleKeydown(event) {\n if (event?.key !== \"Escape\") return;\n event.preventDefault?.();\n event.stopPropagation?.();\n cancelForm();\n }\n\n effect(() => {\n if (focused) return;\n focused = true;\n setTimeout(() => {\n document.getElementById(fieldId())?.focus?.();\n });\n });\n</script>\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import __ce_component$1 from "./input-field.ce.js";
2
+ import { DOMContainer, DOMElement, computed, cond, h, useDefineEmits, useDefineProps, useProps } from "canvasengine";
3
+ //#region src/components/gui/input.ce
4
+ function component($$props) {
5
+ useProps($$props);
6
+ const defineProps = useDefineProps($$props);
7
+ useDefineEmits($$props);
8
+ const { data, onInteraction } = defineProps();
9
+ const title = computed(() => data().title || "");
10
+ function submit(payload) {
11
+ onInteraction?.("submit", payload);
12
+ }
13
+ function cancel() {
14
+ onInteraction?.("cancel");
15
+ }
16
+ return h(DOMContainer, {
17
+ width: "100%",
18
+ height: "100%"
19
+ }, h(DOMElement, {
20
+ element: "div",
21
+ attrs: { class: "rpg-ui-input-layer" }
22
+ }, h(DOMElement, {
23
+ element: "div",
24
+ attrs: { class: "rpg-ui-input-dialog rpg-ui-window rpg-anim-fade-in" }
25
+ }, [cond(computed(() => title()), () => h(DOMElement, {
26
+ element: "div",
27
+ attrs: { class: "rpg-ui-window-title" },
28
+ textContent: computed(() => title())
29
+ })), h(__ce_component$1, {
30
+ data,
31
+ layout: "standalone",
32
+ submit,
33
+ cancel
34
+ })])));
35
+ }
36
+ var __ce_component = component;
37
+ //#endregion
38
+ export { __ce_component as default };
39
+
40
+ //# sourceMappingURL=input.ce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.ce.js","names":[],"sources":["../../../src/components/gui/input.ce"],"sourcesContent":["<DOMContainer width=\"100%\" height=\"100%\">\n <div class=\"rpg-ui-input-layer\">\n <div class=\"rpg-ui-input-dialog rpg-ui-window rpg-anim-fade-in\">\n @if (title()) {\n <div class=\"rpg-ui-window-title\">{title()}</div>\n }\n <InputField data={data} layout=\"standalone\" submit={submit} cancel={cancel} />\n </div>\n </div>\n</DOMContainer>\n\n<script>\n import { computed } from \"canvasengine\";\n import InputField from \"./input-field.ce\";\n\n const { data, onInteraction } = defineProps();\n const title = computed(() => data().title || \"\");\n\n function submit(payload) {\n onInteraction?.(\"submit\", payload);\n }\n\n function cancel() {\n onInteraction?.(\"cancel\");\n }\n</script>\n"],"mappings":";;;AAQI,SAAG,UAAA,SAAA;CACO,SAAA,OAAA;;CAEP,eAAA,OAAA;CACC,MAAG,EAAA,MAAW,kBAAkB,YAAC;CACzC,MAAE,QAAO,eAAmB,KAAM,EAAA,SAAS,EAAA;;EAEzC,gBAAc,UAAc,OAAI;CAClC;;EAEE,gBAAgB,QAAS;CAC3B;QACE,EAAA,cAAA;EAAA,OAAA;EAAA,QAAA;CAAA,GAAA,EAAA,YAAA;EAAA,SAAA;EAAA,OAAA,EAAA,OAAA,qBAAA;CAAA,GAAA,EAAA,YAAA;EAAA,SAAA;EAAA,OAAA,EAAA,OAAA,qDAAA;CAAA,GAAA,CAAA,KAAA,eAAA,MAAA,CAAA,SAAA,EAAA,YAAA;EAAA,SAAA;EAAA,OAAA,EAAA,OAAA,sBAAA;EAAA,aAAA,eAAA,MAAA,CAAA;CAAA,CAAA,CAAA,GAAA,EAAA,kBAAA;EAAA;EAAA,QAAA;EAAA;EAAA;CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AAEA;AAEA,IAAA,iBAAA"}
package/dist/i18n.d.ts CHANGED
@@ -39,6 +39,17 @@ export declare const RpgClientBuiltinI18n: {
39
39
  "rpg.gameover.title": string;
40
40
  "rpg.gameover.title-screen": string;
41
41
  "rpg.gameover.load-game": string;
42
+ "rpg.input.confirm": string;
43
+ "rpg.input.cancel": string;
44
+ "rpg.input.error.invalid": string;
45
+ "rpg.input.error.required": string;
46
+ "rpg.input.error.number": string;
47
+ "rpg.input.error.min": string;
48
+ "rpg.input.error.max": string;
49
+ "rpg.input.error.step": string;
50
+ "rpg.input.error.min-length": string;
51
+ "rpg.input.error.max-length": string;
52
+ "rpg.input.error.email": string;
42
53
  "rpg.shop.default-message": string;
43
54
  "rpg.shop.choose-action": string;
44
55
  "rpg.shop.buy": string;
package/dist/i18n.js CHANGED
@@ -39,6 +39,17 @@ var RpgClientBuiltinI18n = { en: {
39
39
  "rpg.gameover.title": "Game Over",
40
40
  "rpg.gameover.title-screen": "Title Screen",
41
41
  "rpg.gameover.load-game": "Load Game",
42
+ "rpg.input.confirm": "Confirm",
43
+ "rpg.input.cancel": "Cancel",
44
+ "rpg.input.error.invalid": "Invalid value.",
45
+ "rpg.input.error.required": "A value is required.",
46
+ "rpg.input.error.number": "Enter a valid number.",
47
+ "rpg.input.error.min": "The value must be at least {min}.",
48
+ "rpg.input.error.max": "The value must be at most {max}.",
49
+ "rpg.input.error.step": "The value must use increments of {step}.",
50
+ "rpg.input.error.min-length": "Enter at least {minLength} characters.",
51
+ "rpg.input.error.max-length": "Enter no more than {maxLength} characters.",
52
+ "rpg.input.error.email": "Enter a valid email address.",
42
53
  "rpg.shop.default-message": "Welcome to my shop!",
43
54
  "rpg.shop.choose-action": "Choose an action",
44
55
  "rpg.shop.buy": "Buy",
package/dist/i18n.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","names":[],"sources":["../src/i18n.ts"],"sourcesContent":["import { createI18nProvider, type I18nConfig } from \"@rpgjs/common\";\n\nexport const RpgClientBuiltinI18n = {\n en: {\n \"rpg.menu.title\": \"Menu\",\n \"rpg.menu.status\": \"Status\",\n \"rpg.menu.level\": \"Level\",\n \"rpg.menu.gold\": \"Gold\",\n \"rpg.menu.parameters\": \"Parameters\",\n \"rpg.menu.items\": \"Items\",\n \"rpg.menu.skills\": \"Skills\",\n \"rpg.menu.equip\": \"Equip\",\n \"rpg.menu.options\": \"Options\",\n \"rpg.menu.save\": \"Save\",\n \"rpg.menu.exit\": \"Exit\",\n \"rpg.menu.weapons\": \"Weapons\",\n \"rpg.menu.armor\": \"Armor\",\n \"rpg.menu.use\": \"Use\",\n \"rpg.menu.cancel\": \"Cancel\",\n \"rpg.menu.unequip\": \"Unequip\",\n \"rpg.menu.remove-equipment\": \"Remove the current equipment\",\n \"rpg.menu.empty\": \"Empty\",\n \"rpg.menu.leave-game\": \"Leave the game?\",\n \"rpg.menu.exit-help\": \"Press Action to confirm or Escape to go back.\",\n \"rpg.menu.options-help\": \"Configure your preferences here.\",\n \"rpg.save.title\": \"Save Game\",\n \"rpg.save.subtitle\": \"Choose a slot to overwrite or create.\",\n \"rpg.load.title\": \"Load Game\",\n \"rpg.load.subtitle\": \"Select a slot to load your progress.\",\n \"rpg.save.auto\": \"Auto Save\",\n \"rpg.save.slot\": \"Slot {index}\",\n \"rpg.save.empty-slot\": \"Empty Slot\",\n \"rpg.save.level\": \"Level\",\n \"rpg.save.exp\": \"Exp\",\n \"rpg.save.map\": \"Map\",\n \"rpg.save.date\": \"Date\",\n \"rpg.title.default\": \"RPG\",\n \"rpg.title.start\": \"Start\",\n \"rpg.title.load\": \"Load\",\n \"rpg.gameover.title\": \"Game Over\",\n \"rpg.gameover.title-screen\": \"Title Screen\",\n \"rpg.gameover.load-game\": \"Load Game\",\n \"rpg.shop.default-message\": \"Welcome to my shop!\",\n \"rpg.shop.choose-action\": \"Choose an action\",\n \"rpg.shop.buy\": \"Buy\",\n \"rpg.shop.sell\": \"Sell\",\n \"rpg.shop.back\": \"Back\",\n \"rpg.shop.equipped\": \"Equipped\",\n \"rpg.shop.already-equipped\": \"Already equipped\",\n \"rpg.shop.qty\": \"Qty\",\n \"rpg.shop.available\": \"Available\",\n \"rpg.shop.quantity\": \"Quantity\",\n \"rpg.shop.total\": \"Total\",\n },\n};\n\nexport function provideI18n(config: I18nConfig = {}) {\n return createI18nProvider(config);\n}\n"],"mappings":";;AAEA,IAAa,uBAAuB,EAClC,IAAI;CACF,kBAAkB;CAClB,mBAAmB;CACnB,kBAAkB;CAClB,iBAAiB;CACjB,uBAAuB;CACvB,kBAAkB;CAClB,mBAAmB;CACnB,kBAAkB;CAClB,oBAAoB;CACpB,iBAAiB;CACjB,iBAAiB;CACjB,oBAAoB;CACpB,kBAAkB;CAClB,gBAAgB;CAChB,mBAAmB;CACnB,oBAAoB;CACpB,6BAA6B;CAC7B,kBAAkB;CAClB,uBAAuB;CACvB,sBAAsB;CACtB,yBAAyB;CACzB,kBAAkB;CAClB,qBAAqB;CACrB,kBAAkB;CAClB,qBAAqB;CACrB,iBAAiB;CACjB,iBAAiB;CACjB,uBAAuB;CACvB,kBAAkB;CAClB,gBAAgB;CAChB,gBAAgB;CAChB,iBAAiB;CACjB,qBAAqB;CACrB,mBAAmB;CACnB,kBAAkB;CAClB,sBAAsB;CACtB,6BAA6B;CAC7B,0BAA0B;CAC1B,4BAA4B;CAC5B,0BAA0B;CAC1B,gBAAgB;CAChB,iBAAiB;CACjB,iBAAiB;CACjB,qBAAqB;CACrB,6BAA6B;CAC7B,gBAAgB;CAChB,sBAAsB;CACtB,qBAAqB;CACrB,kBAAkB;AACpB,EACF;AAEA,SAAgB,YAAY,SAAqB,CAAC,GAAG;CACnD,OAAO,mBAAmB,MAAM;AAClC"}
1
+ {"version":3,"file":"i18n.js","names":[],"sources":["../src/i18n.ts"],"sourcesContent":["import { createI18nProvider, type I18nConfig } from \"@rpgjs/common\";\n\nexport const RpgClientBuiltinI18n = {\n en: {\n \"rpg.menu.title\": \"Menu\",\n \"rpg.menu.status\": \"Status\",\n \"rpg.menu.level\": \"Level\",\n \"rpg.menu.gold\": \"Gold\",\n \"rpg.menu.parameters\": \"Parameters\",\n \"rpg.menu.items\": \"Items\",\n \"rpg.menu.skills\": \"Skills\",\n \"rpg.menu.equip\": \"Equip\",\n \"rpg.menu.options\": \"Options\",\n \"rpg.menu.save\": \"Save\",\n \"rpg.menu.exit\": \"Exit\",\n \"rpg.menu.weapons\": \"Weapons\",\n \"rpg.menu.armor\": \"Armor\",\n \"rpg.menu.use\": \"Use\",\n \"rpg.menu.cancel\": \"Cancel\",\n \"rpg.menu.unequip\": \"Unequip\",\n \"rpg.menu.remove-equipment\": \"Remove the current equipment\",\n \"rpg.menu.empty\": \"Empty\",\n \"rpg.menu.leave-game\": \"Leave the game?\",\n \"rpg.menu.exit-help\": \"Press Action to confirm or Escape to go back.\",\n \"rpg.menu.options-help\": \"Configure your preferences here.\",\n \"rpg.save.title\": \"Save Game\",\n \"rpg.save.subtitle\": \"Choose a slot to overwrite or create.\",\n \"rpg.load.title\": \"Load Game\",\n \"rpg.load.subtitle\": \"Select a slot to load your progress.\",\n \"rpg.save.auto\": \"Auto Save\",\n \"rpg.save.slot\": \"Slot {index}\",\n \"rpg.save.empty-slot\": \"Empty Slot\",\n \"rpg.save.level\": \"Level\",\n \"rpg.save.exp\": \"Exp\",\n \"rpg.save.map\": \"Map\",\n \"rpg.save.date\": \"Date\",\n \"rpg.title.default\": \"RPG\",\n \"rpg.title.start\": \"Start\",\n \"rpg.title.load\": \"Load\",\n \"rpg.gameover.title\": \"Game Over\",\n \"rpg.gameover.title-screen\": \"Title Screen\",\n \"rpg.gameover.load-game\": \"Load Game\",\n \"rpg.input.confirm\": \"Confirm\",\n \"rpg.input.cancel\": \"Cancel\",\n \"rpg.input.error.invalid\": \"Invalid value.\",\n \"rpg.input.error.required\": \"A value is required.\",\n \"rpg.input.error.number\": \"Enter a valid number.\",\n \"rpg.input.error.min\": \"The value must be at least {min}.\",\n \"rpg.input.error.max\": \"The value must be at most {max}.\",\n \"rpg.input.error.step\": \"The value must use increments of {step}.\",\n \"rpg.input.error.min-length\": \"Enter at least {minLength} characters.\",\n \"rpg.input.error.max-length\": \"Enter no more than {maxLength} characters.\",\n \"rpg.input.error.email\": \"Enter a valid email address.\",\n \"rpg.shop.default-message\": \"Welcome to my shop!\",\n \"rpg.shop.choose-action\": \"Choose an action\",\n \"rpg.shop.buy\": \"Buy\",\n \"rpg.shop.sell\": \"Sell\",\n \"rpg.shop.back\": \"Back\",\n \"rpg.shop.equipped\": \"Equipped\",\n \"rpg.shop.already-equipped\": \"Already equipped\",\n \"rpg.shop.qty\": \"Qty\",\n \"rpg.shop.available\": \"Available\",\n \"rpg.shop.quantity\": \"Quantity\",\n \"rpg.shop.total\": \"Total\",\n },\n};\n\nexport function provideI18n(config: I18nConfig = {}) {\n return createI18nProvider(config);\n}\n"],"mappings":";;AAEA,IAAa,uBAAuB,EAClC,IAAI;CACF,kBAAkB;CAClB,mBAAmB;CACnB,kBAAkB;CAClB,iBAAiB;CACjB,uBAAuB;CACvB,kBAAkB;CAClB,mBAAmB;CACnB,kBAAkB;CAClB,oBAAoB;CACpB,iBAAiB;CACjB,iBAAiB;CACjB,oBAAoB;CACpB,kBAAkB;CAClB,gBAAgB;CAChB,mBAAmB;CACnB,oBAAoB;CACpB,6BAA6B;CAC7B,kBAAkB;CAClB,uBAAuB;CACvB,sBAAsB;CACtB,yBAAyB;CACzB,kBAAkB;CAClB,qBAAqB;CACrB,kBAAkB;CAClB,qBAAqB;CACrB,iBAAiB;CACjB,iBAAiB;CACjB,uBAAuB;CACvB,kBAAkB;CAClB,gBAAgB;CAChB,gBAAgB;CAChB,iBAAiB;CACjB,qBAAqB;CACrB,mBAAmB;CACnB,kBAAkB;CAClB,sBAAsB;CACtB,6BAA6B;CAC7B,0BAA0B;CAC1B,qBAAqB;CACrB,oBAAoB;CACpB,2BAA2B;CAC3B,4BAA4B;CAC5B,0BAA0B;CAC1B,uBAAuB;CACvB,uBAAuB;CACvB,wBAAwB;CACxB,8BAA8B;CAC9B,8BAA8B;CAC9B,yBAAyB;CACzB,4BAA4B;CAC5B,0BAA0B;CAC1B,gBAAgB;CAChB,iBAAiB;CACjB,iBAAiB;CACjB,qBAAqB;CACrB,6BAA6B;CAC7B,gBAAgB;CAChB,sBAAsB;CACtB,qBAAqB;CACrB,kBAAkB;AACpB,EACF;AAEA,SAAgB,YAAY,SAAqB,CAAC,GAAG;CACnD,OAAO,mBAAmB,MAAM;AAClC"}
package/dist/index.js CHANGED
@@ -2,25 +2,27 @@ import { Context } from "./node_modules/.pnpm/@signe_di@3.1.0/node_modules/@sign
2
2
  import { clearInject, context, inject, setInject } from "./core/inject.js";
3
3
  import { AbstractWebsocket, WebSocketToken } from "./services/AbstractSocket.js";
4
4
  import { getKeyboardControlBind, isKeyboardActionConfig, keyboardEventMatchesBind, normalizeActionInput, resolveKeyboardActionInput, resolveKeyboardDirectionInput } from "./services/actionInput.js";
5
+ import __ce_component$10 from "./components/gui/input-field.ce.js";
5
6
  import __ce_component$2 from "./components/gui/dialogbox/index.ce.js";
6
7
  import __ce_component from "./components/gui/box.ce.js";
7
- import __ce_component$16 from "./components/gui/shop/shop.ce.js";
8
+ import __ce_component$18 from "./components/gui/shop/shop.ce.js";
8
9
  import { getEntityProp } from "./utils/getEntityProp.js";
9
10
  import __ce_component$8 from "./components/gui/hud/hud.ce.js";
10
11
  import { SaveClientService, SaveClientToken, provideSaveClient } from "./services/save.js";
11
- import __ce_component$14 from "./components/gui/save-load.ce.js";
12
- import __ce_component$9 from "./components/gui/menu/items-menu.ce.js";
13
- import __ce_component$17 from "./components/gui/menu/skills-menu.ce.js";
12
+ import __ce_component$16 from "./components/gui/save-load.ce.js";
13
+ import __ce_component$11 from "./components/gui/menu/items-menu.ce.js";
14
+ import __ce_component$19 from "./components/gui/menu/skills-menu.ce.js";
14
15
  import __ce_component$3 from "./components/gui/menu/equip-menu.ce.js";
15
- import __ce_component$13 from "./components/gui/menu/options-menu.ce.js";
16
+ import __ce_component$15 from "./components/gui/menu/options-menu.ce.js";
16
17
  import __ce_component$5 from "./components/gui/menu/exit-menu.ce.js";
17
- import __ce_component$11 from "./components/gui/menu/main-menu.ce.js";
18
- import __ce_component$12 from "./components/gui/notification/notification.ce.js";
19
- import __ce_component$18 from "./components/gui/title-screen.ce.js";
18
+ import __ce_component$13 from "./components/gui/menu/main-menu.ce.js";
19
+ import __ce_component$14 from "./components/gui/notification/notification.ce.js";
20
+ import __ce_component$20 from "./components/gui/title-screen.ce.js";
20
21
  import __ce_component$6 from "./components/gui/gameover.ce.js";
22
+ import __ce_component$9 from "./components/gui/input.ce.js";
21
23
  import "./components/gui/index.js";
22
24
  import { RpgGui } from "./Gui/Gui.js";
23
- import __ce_component$15 from "./components/scenes/draw-map.ce.js";
25
+ import __ce_component$17 from "./components/scenes/draw-map.ce.js";
24
26
  import { LoadMapService, LoadMapToken, provideLoadMap } from "./services/loadMap.js";
25
27
  import { RpgSound, Sound, getSoundMetadata } from "./Sound.js";
26
28
  import { RpgResource } from "./Resource.js";
@@ -40,7 +42,7 @@ import { provideRpg } from "./services/standalone.js";
40
42
  import { BridgeWebsocket, provideMmorpg } from "./services/mmorpg.js";
41
43
  import { startGame } from "./core/setup.js";
42
44
  import __ce_component$7 from "./components/prebuilt/hp-bar.ce.js";
43
- import __ce_component$10 from "./components/prebuilt/light-halo.ce.js";
45
+ import __ce_component$12 from "./components/prebuilt/light-halo.ce.js";
44
46
  import "./components/prebuilt/index.js";
45
47
  import { Presets } from "./presets/index.js";
46
48
  import __ce_component$1 from "./components/character.ce.js";
@@ -49,4 +51,4 @@ import "./components/index.js";
49
51
  import { Spritesheet } from "./decorators/spritesheet.js";
50
52
  import { withMobile } from "./components/gui/mobile/index.js";
51
53
  import { Input, KeyboardControls } from "canvasengine";
52
- export { AbstractWebsocket, __ce_component as BoxComponent, BridgeWebsocket, __ce_component$1 as CharacterComponent, ClientVisualRegistry, Context, Control, __ce_component$2 as DialogboxComponent, __ce_component$3 as EquipMenuComponent, __ce_component$4 as EventLayerComponent, __ce_component$5 as ExitMenuComponent, __ce_component$6 as GameoverComponent, GlobalConfigToken, __ce_component$7 as HpBar, __ce_component$8 as HudComponent, Input, __ce_component$9 as ItemsMenuComponent, KeyboardControls, __ce_component$10 as LightHalo, LoadMapService, LoadMapToken, __ce_component$11 as MainMenuComponent, __ce_component$12 as NotificationComponent, __ce_component$13 as OptionsMenuComponent, PrebuiltComponentAnimations, Presets, ProjectileManager, RpgClientBuiltinI18n, RpgClientEngine, RpgClientEvent, RpgClientInteractions, RpgClientObject, RpgClientPlayer, RpgGui, RpgResource, RpgSound, SaveClientService, SaveClientToken, __ce_component$14 as SaveLoadComponent, __ce_component$15 as SceneMap, __ce_component$16 as ShopComponent, __ce_component$17 as SkillsMenuComponent, Sound, Spritesheet, __ce_component$18 as TitleScreenComponent, WebSocketToken, clearInject, context, createClientPointerContext, dragToTile, draggable, getEntityProp, getKeyboardControlBind, getSoundMetadata, hoverPopover, inject, isKeyboardActionConfig, keyboardEventMatchesBind, normalizeActionInput, provideClientGlobalConfig, provideClientModules, provideGlobalConfig, provideI18n, provideLoadMap, provideMmorpg, provideRpg, provideSaveClient, resolveKeyboardActionInput, resolveKeyboardDirectionInput, selectable, setInject, startGame, withMobile };
54
+ export { AbstractWebsocket, __ce_component as BoxComponent, BridgeWebsocket, __ce_component$1 as CharacterComponent, ClientVisualRegistry, Context, Control, __ce_component$2 as DialogboxComponent, __ce_component$3 as EquipMenuComponent, __ce_component$4 as EventLayerComponent, __ce_component$5 as ExitMenuComponent, __ce_component$6 as GameoverComponent, GlobalConfigToken, __ce_component$7 as HpBar, __ce_component$8 as HudComponent, Input, __ce_component$9 as InputComponent, __ce_component$10 as InputFieldComponent, __ce_component$11 as ItemsMenuComponent, KeyboardControls, __ce_component$12 as LightHalo, LoadMapService, LoadMapToken, __ce_component$13 as MainMenuComponent, __ce_component$14 as NotificationComponent, __ce_component$15 as OptionsMenuComponent, PrebuiltComponentAnimations, Presets, ProjectileManager, RpgClientBuiltinI18n, RpgClientEngine, RpgClientEvent, RpgClientInteractions, RpgClientObject, RpgClientPlayer, RpgGui, RpgResource, RpgSound, SaveClientService, SaveClientToken, __ce_component$16 as SaveLoadComponent, __ce_component$17 as SceneMap, __ce_component$18 as ShopComponent, __ce_component$19 as SkillsMenuComponent, Sound, Spritesheet, __ce_component$20 as TitleScreenComponent, WebSocketToken, clearInject, context, createClientPointerContext, dragToTile, draggable, getEntityProp, getKeyboardControlBind, getSoundMetadata, hoverPopover, inject, isKeyboardActionConfig, keyboardEventMatchesBind, normalizeActionInput, provideClientGlobalConfig, provideClientModules, provideGlobalConfig, provideI18n, provideLoadMap, provideMmorpg, provideRpg, provideSaveClient, resolveKeyboardActionInput, resolveKeyboardDirectionInput, selectable, setInject, startGame, withMobile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/client",
3
- "version": "5.0.0-beta.24",
3
+ "version": "5.0.0-beta.25",
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",
@@ -22,9 +22,9 @@
22
22
  "pixi.js": "^8.9.2"
23
23
  },
24
24
  "dependencies": {
25
- "@rpgjs/common": "5.0.0-beta.23",
26
- "@rpgjs/server": "5.0.0-beta.24",
27
- "@rpgjs/ui-css": "5.0.0-beta.22",
25
+ "@rpgjs/common": "5.0.0-beta.24",
26
+ "@rpgjs/server": "5.0.0-beta.25",
27
+ "@rpgjs/ui-css": "5.0.0-beta.23",
28
28
  "@signe/di": "3.1.0",
29
29
  "@signe/room": "3.1.0",
30
30
  "@signe/sync": "3.1.0",
@@ -14,6 +14,7 @@ vi.mock("../components/gui", () => {
14
14
  NotificationComponent: component,
15
15
  TitleScreenComponent: component,
16
16
  GameoverComponent: component,
17
+ InputComponent: component,
17
18
  };
18
19
  });
19
20
 
@@ -64,6 +65,10 @@ const VueTooltip = {
64
65
  };
65
66
 
66
67
  describe("RpgGui Vue integration", () => {
68
+ test("registers the prebuilt input GUI", async () => {
69
+ const { gui } = await createGui();
70
+ expect(gui.get(PrebuiltGui.Input)).toBeDefined();
71
+ });
67
72
  test("tracks GUI open ids and sends them back when closing", async () => {
68
73
  const { gui, socket } = await createGui();
69
74
  await gui._initialize();
package/src/Gui/Gui.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Context, inject } from "@signe/di";
2
2
  import { signal, Signal, WritableSignal } from "canvasengine";
3
3
  import { AbstractWebsocket, WebSocketToken } from "../services/AbstractSocket";
4
- import { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from "../components/gui";
4
+ import { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent, InputComponent } from "../components/gui";
5
5
  import { combineLatest, Subscription } from "rxjs";
6
6
  import { PrebuiltGui } from "@rpgjs/common";
7
7
 
@@ -176,6 +176,10 @@ export class RpgGui {
176
176
  name: PrebuiltGui.Gameover,
177
177
  component: GameoverComponent,
178
178
  });
179
+ this.add({
180
+ name: PrebuiltGui.Input,
181
+ component: InputComponent,
182
+ });
179
183
 
180
184
  this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);
181
185
  }
@@ -31,6 +31,15 @@
31
31
  </div>
32
32
  </Navigation>
33
33
  }
34
+ @if (hasInput()) {
35
+ <InputField
36
+ data={dialogInput}
37
+ layout="dialog"
38
+ showMessage={false}
39
+ submit={submitInput}
40
+ cancel={cancelInput}
41
+ />
42
+ }
34
43
  </div>
35
44
  @if (hasFace()) {
36
45
  <div class="rpg-ui-dialog-face">
@@ -57,6 +66,7 @@
57
66
  import { RpgClientEngine } from "../../../RpgClientEngine";
58
67
  import { delay } from "@rpgjs/common";
59
68
  import { getKeyboardControlBind } from "../../../services/actionInput";
69
+ import InputField from "../input-field.ce";
60
70
 
61
71
  const engine = inject(RpgClientEngine);
62
72
  const currentPlayer = engine.scene.currentPlayer
@@ -82,6 +92,8 @@
82
92
  const position = computed(() => dialogData().position);
83
93
  const typewriterEffect = computed(() => dialogData().typewriterEffect);
84
94
  const fullWidth = computed(() => dialogData().fullWidth || false);
95
+ const dialogInput = computed(() => dialogData().input || null);
96
+ const hasInput = computed(() => !!dialogInput());
85
97
 
86
98
  const resolveProp = (value) => typeof value === "function" ? value() : value;
87
99
  const normalizeOpenId = (value) => {
@@ -149,7 +161,7 @@
149
161
 
150
162
 
151
163
  const hasChoices = computed(() => dialogChoices().length > 0);
152
- const showIndicator = computed(() => !hasChoices() && !isTyping());
164
+ const showIndicator = computed(() => !hasChoices() && !hasInput() && !isTyping());
153
165
  const nav = createTabindexNavigator(selectedItem, { count: () => dialogChoices().length }, 'wrap');
154
166
 
155
167
  function selectChoice(index) {
@@ -168,6 +180,10 @@
168
180
  };
169
181
 
170
182
  const closeDialog = () => {
183
+ if (hasInput()) {
184
+ onInteraction?.("cancel");
185
+ return;
186
+ }
171
187
  _onFinish();
172
188
  };
173
189
 
@@ -180,6 +196,14 @@
180
196
  _onFinish(index);
181
197
  };
182
198
 
199
+ const submitInput = (payload) => {
200
+ onInteraction?.("submit", payload);
201
+ };
202
+
203
+ const cancelInput = () => {
204
+ onInteraction?.("cancel");
205
+ };
206
+
183
207
  const canAcceptAction = () => Date.now() - openedAt >= ACTION_OPEN_GUARD_MS;
184
208
 
185
209
  const controls = signal({
@@ -231,6 +255,7 @@
231
255
  finishTyping();
232
256
  return;
233
257
  }
258
+ if (hasInput()) return;
234
259
  if (hasChoices()) return;
235
260
  _onFinish();
236
261
  }
@@ -12,6 +12,8 @@ import ExitMenuComponent from "./menu/exit-menu.ce";
12
12
  import NotificationComponent from "./notification/notification.ce";
13
13
  import TitleScreenComponent from "./title-screen.ce";
14
14
  import GameoverComponent from "./gameover.ce";
15
+ import InputComponent from "./input.ce";
16
+ import InputFieldComponent from "./input-field.ce";
15
17
 
16
18
  export {
17
19
  DialogboxComponent,
@@ -27,5 +29,7 @@ export {
27
29
  ExitMenuComponent,
28
30
  NotificationComponent,
29
31
  TitleScreenComponent,
30
- GameoverComponent
32
+ GameoverComponent,
33
+ InputComponent,
34
+ InputFieldComponent
31
35
  }
@@ -0,0 +1,106 @@
1
+ <form
2
+ class="rpg-ui-input-form"
3
+ data-layout={layout()}
4
+ submit={submitForm}
5
+ keydown={handleKeydown}
6
+ >
7
+ @if (showMessage()) {
8
+ <label class="rpg-ui-input-message" for={fieldId()}>{formData().message}</label>
9
+ }
10
+ @if (formData().control === "textarea") {
11
+ <textarea
12
+ id={fieldId()}
13
+ class="rpg-ui-input rpg-ui-input-dialog-field"
14
+ name="value"
15
+ value={value}
16
+ placeholder={formData().placeholder || ""}
17
+ rows={formData().rows || 4}
18
+ minlength={formData().minLength}
19
+ maxlength={formData().maxLength}
20
+ aria-required={formData().required ? "true" : "false"}
21
+ ></textarea>
22
+ }
23
+ @else {
24
+ <input
25
+ id={fieldId()}
26
+ class="rpg-ui-input rpg-ui-input-dialog-field"
27
+ name="value"
28
+ type={formData().type || "text"}
29
+ value={value}
30
+ placeholder={formData().placeholder || ""}
31
+ min={formData().min}
32
+ max={formData().max}
33
+ step={formData().step}
34
+ minlength={formData().minLength}
35
+ maxlength={formData().maxLength}
36
+ aria-required={formData().required ? "true" : "false"}
37
+ />
38
+ }
39
+ @if (errorText()) {
40
+ <div class="rpg-ui-input-error" role="alert">{errorText()}</div>
41
+ }
42
+ <div class="rpg-ui-input-actions">
43
+ @if (showCancelButton()) {
44
+ <button class="rpg-ui-btn rpg-ui-input-button rpg-ui-input-button-cancel" type="button" click={cancelForm}>
45
+ {formData().cancelText || t("rpg.input.cancel")}
46
+ </button>
47
+ }
48
+ <button
49
+ class="rpg-ui-btn rpg-ui-input-button rpg-ui-input-button-confirm"
50
+ data-variant="primary"
51
+ type="submit"
52
+ >
53
+ {formData().confirmText || t("rpg.input.confirm")}
54
+ </button>
55
+ </div>
56
+ </form>
57
+
58
+ <script>
59
+ import { computed, effect, signal } from "canvasengine";
60
+ import { inject } from "../../core/inject";
61
+ import { RpgClientEngine } from "../../RpgClientEngine";
62
+
63
+ const engine = inject(RpgClientEngine);
64
+ const { t } = engine.i18n();
65
+ const { data, layout, showMessage } = defineProps({
66
+ layout: "standalone",
67
+ showMessage: true,
68
+ });
69
+ const { submit, cancel } = defineEmits();
70
+ const formData = computed(() => data() || {});
71
+ const value = signal(String(formData().defaultValue ?? ""));
72
+ const fieldId = computed(() => `rpg-input-value-${layout()}`);
73
+ const errorText = computed(() => formData().errorKey
74
+ ? t(formData().errorKey, formData().errorParams || {})
75
+ : ""
76
+ );
77
+ const showCancelButton = computed(() => formData().cancelButton !== false);
78
+ let focused = false;
79
+
80
+ function submitForm(event, collectedData) {
81
+ const submittedValue = collectedData && Object.prototype.hasOwnProperty.call(collectedData, "value")
82
+ ? collectedData.value
83
+ : event?.currentTarget?.elements?.value?.value ?? value();
84
+ value.set(String(submittedValue ?? ""));
85
+ submit({ value: submittedValue ?? "" });
86
+ }
87
+
88
+ function cancelForm() {
89
+ cancel();
90
+ }
91
+
92
+ function handleKeydown(event) {
93
+ if (event?.key !== "Escape") return;
94
+ event.preventDefault?.();
95
+ event.stopPropagation?.();
96
+ cancelForm();
97
+ }
98
+
99
+ effect(() => {
100
+ if (focused) return;
101
+ focused = true;
102
+ setTimeout(() => {
103
+ document.getElementById(fieldId())?.focus?.();
104
+ });
105
+ });
106
+ </script>
@@ -0,0 +1,26 @@
1
+ <DOMContainer width="100%" height="100%">
2
+ <div class="rpg-ui-input-layer">
3
+ <div class="rpg-ui-input-dialog rpg-ui-window rpg-anim-fade-in">
4
+ @if (title()) {
5
+ <div class="rpg-ui-window-title">{title()}</div>
6
+ }
7
+ <InputField data={data} layout="standalone" submit={submit} cancel={cancel} />
8
+ </div>
9
+ </div>
10
+ </DOMContainer>
11
+
12
+ <script>
13
+ import { computed } from "canvasengine";
14
+ import InputField from "./input-field.ce";
15
+
16
+ const { data, onInteraction } = defineProps();
17
+ const title = computed(() => data().title || "");
18
+
19
+ function submit(payload) {
20
+ onInteraction?.("submit", payload);
21
+ }
22
+
23
+ function cancel() {
24
+ onInteraction?.("cancel");
25
+ }
26
+ </script>
package/src/i18n.ts CHANGED
@@ -40,6 +40,17 @@ export const RpgClientBuiltinI18n = {
40
40
  "rpg.gameover.title": "Game Over",
41
41
  "rpg.gameover.title-screen": "Title Screen",
42
42
  "rpg.gameover.load-game": "Load Game",
43
+ "rpg.input.confirm": "Confirm",
44
+ "rpg.input.cancel": "Cancel",
45
+ "rpg.input.error.invalid": "Invalid value.",
46
+ "rpg.input.error.required": "A value is required.",
47
+ "rpg.input.error.number": "Enter a valid number.",
48
+ "rpg.input.error.min": "The value must be at least {min}.",
49
+ "rpg.input.error.max": "The value must be at most {max}.",
50
+ "rpg.input.error.step": "The value must use increments of {step}.",
51
+ "rpg.input.error.min-length": "Enter at least {minLength} characters.",
52
+ "rpg.input.error.max-length": "Enter no more than {maxLength} characters.",
53
+ "rpg.input.error.email": "Enter a valid email address.",
43
54
  "rpg.shop.default-message": "Welcome to my shop!",
44
55
  "rpg.shop.choose-action": "Choose an action",
45
56
  "rpg.shop.buy": "Buy",
package/vite.config.ts CHANGED
@@ -11,7 +11,10 @@ export default defineConfig({
11
11
  canvasengine(),
12
12
  dts({
13
13
  include: ['src/**/*.ts'],
14
- outDir: 'dist'
14
+ outDirs: 'dist',
15
+ afterDiagnostic(diagnostics) {
16
+ if (diagnostics.length > 0) throw new Error(`Declaration generation failed with ${diagnostics.length} TypeScript diagnostic(s)`)
17
+ }
15
18
  })
16
19
  ],
17
20
  build: {
@@ -33,4 +36,4 @@ export default defineConfig({
33
36
  }
34
37
  },
35
38
  },
36
- })
39
+ })