@dreamboard-games/ui-sdk 0.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. package/LICENSE +89 -0
  2. package/NOTICE +1 -0
  3. package/README.md +154 -0
  4. package/dist/components/ActionButton.d.ts +13 -0
  5. package/dist/components/ActionButton.d.ts.map +1 -0
  6. package/dist/components/ActionButton.js +14 -0
  7. package/dist/components/ActionPanel.d.ts +33 -0
  8. package/dist/components/ActionPanel.d.ts.map +1 -0
  9. package/dist/components/ActionPanel.js +148 -0
  10. package/dist/components/Card.d.ts +29 -0
  11. package/dist/components/Card.d.ts.map +1 -0
  12. package/dist/components/Card.js +220 -0
  13. package/dist/components/ChromeSuppressionContext.d.ts +7 -0
  14. package/dist/components/ChromeSuppressionContext.d.ts.map +1 -0
  15. package/dist/components/ChromeSuppressionContext.js +34 -0
  16. package/dist/components/CostDisplay.d.ts +22 -0
  17. package/dist/components/CostDisplay.d.ts.map +1 -0
  18. package/dist/components/CostDisplay.js +41 -0
  19. package/dist/components/DiceRoller.d.ts +30 -0
  20. package/dist/components/DiceRoller.d.ts.map +1 -0
  21. package/dist/components/DiceRoller.js +319 -0
  22. package/dist/components/Drawer.d.ts +19 -0
  23. package/dist/components/Drawer.d.ts.map +1 -0
  24. package/dist/components/Drawer.js +55 -0
  25. package/dist/components/ErrorBoundary.d.ts +24 -0
  26. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  27. package/dist/components/ErrorBoundary.js +37 -0
  28. package/dist/components/GameEndDisplay.d.ts +27 -0
  29. package/dist/components/GameEndDisplay.d.ts.map +1 -0
  30. package/dist/components/GameEndDisplay.js +185 -0
  31. package/dist/components/GameSkeleton.d.ts +12 -0
  32. package/dist/components/GameSkeleton.d.ts.map +1 -0
  33. package/dist/components/GameSkeleton.js +54 -0
  34. package/dist/components/Hand.d.ts +99 -0
  35. package/dist/components/Hand.d.ts.map +1 -0
  36. package/dist/components/Hand.js +162 -0
  37. package/dist/components/HandDock.d.ts +35 -0
  38. package/dist/components/HandDock.d.ts.map +1 -0
  39. package/dist/components/HandDock.js +124 -0
  40. package/dist/components/InteractionForm.d.ts +50 -0
  41. package/dist/components/InteractionForm.d.ts.map +1 -0
  42. package/dist/components/InteractionForm.js +402 -0
  43. package/dist/components/MoreActions.d.ts +49 -0
  44. package/dist/components/MoreActions.d.ts.map +1 -0
  45. package/dist/components/MoreActions.js +64 -0
  46. package/dist/components/PhaseIndicator.d.ts +35 -0
  47. package/dist/components/PhaseIndicator.d.ts.map +1 -0
  48. package/dist/components/PhaseIndicator.js +212 -0
  49. package/dist/components/PlayArea.d.ts +28 -0
  50. package/dist/components/PlayArea.d.ts.map +1 -0
  51. package/dist/components/PlayArea.js +48 -0
  52. package/dist/components/PluginRuntime.d.ts +37 -0
  53. package/dist/components/PluginRuntime.d.ts.map +1 -0
  54. package/dist/components/PluginRuntime.js +47 -0
  55. package/dist/components/PrimaryActionButton.d.ts +98 -0
  56. package/dist/components/PrimaryActionButton.d.ts.map +1 -0
  57. package/dist/components/PrimaryActionButton.js +183 -0
  58. package/dist/components/PrimaryButton.d.ts +20 -0
  59. package/dist/components/PrimaryButton.d.ts.map +1 -0
  60. package/dist/components/PrimaryButton.js +5 -0
  61. package/dist/components/PromptDialogHost.d.ts +15 -0
  62. package/dist/components/PromptDialogHost.d.ts.map +1 -0
  63. package/dist/components/PromptDialogHost.js +22 -0
  64. package/dist/components/ResourceCounter.d.ts +38 -0
  65. package/dist/components/ResourceCounter.d.ts.map +1 -0
  66. package/dist/components/ResourceCounter.js +118 -0
  67. package/dist/components/ThemedButton.d.ts +12 -0
  68. package/dist/components/ThemedButton.d.ts.map +1 -0
  69. package/dist/components/ThemedButton.js +38 -0
  70. package/dist/components/Toast.d.ts +35 -0
  71. package/dist/components/Toast.d.ts.map +1 -0
  72. package/dist/components/Toast.js +116 -0
  73. package/dist/components/board/HexGrid.d.ts +344 -0
  74. package/dist/components/board/HexGrid.d.ts.map +1 -0
  75. package/dist/components/board/HexGrid.js +340 -0
  76. package/dist/components/board/NetworkGraph.d.ts +100 -0
  77. package/dist/components/board/NetworkGraph.d.ts.map +1 -0
  78. package/dist/components/board/NetworkGraph.js +123 -0
  79. package/dist/components/board/SlotSystem.d.ts +71 -0
  80. package/dist/components/board/SlotSystem.d.ts.map +1 -0
  81. package/dist/components/board/SlotSystem.js +87 -0
  82. package/dist/components/board/SquareGrid.d.ts +188 -0
  83. package/dist/components/board/SquareGrid.d.ts.map +1 -0
  84. package/dist/components/board/SquareGrid.js +328 -0
  85. package/dist/components/board/TrackBoard.d.ts +113 -0
  86. package/dist/components/board/TrackBoard.d.ts.map +1 -0
  87. package/dist/components/board/TrackBoard.js +135 -0
  88. package/dist/components/board/ZoneMap.d.ts +88 -0
  89. package/dist/components/board/ZoneMap.d.ts.map +1 -0
  90. package/dist/components/board/ZoneMap.js +133 -0
  91. package/dist/components/board/hex-board-view.d.ts +69 -0
  92. package/dist/components/board/hex-board-view.d.ts.map +1 -0
  93. package/dist/components/board/hex-board-view.js +60 -0
  94. package/dist/components/board/index.d.ts +23 -0
  95. package/dist/components/board/index.d.ts.map +1 -0
  96. package/dist/components/board/index.js +40 -0
  97. package/dist/components/board/interaction-accessibility.d.ts +5 -0
  98. package/dist/components/board/interaction-accessibility.d.ts.map +1 -0
  99. package/dist/components/board/interaction-accessibility.js +13 -0
  100. package/dist/components/board/target-layer.d.ts +13 -0
  101. package/dist/components/board/target-layer.d.ts.map +1 -0
  102. package/dist/components/board/target-layer.js +10 -0
  103. package/dist/components/card-render-content.type-test.d.ts +2 -0
  104. package/dist/components/card-render-content.type-test.d.ts.map +1 -0
  105. package/dist/components/card-render-content.type-test.js +1 -0
  106. package/dist/components/index.d.ts +34 -0
  107. package/dist/components/index.d.ts.map +1 -0
  108. package/dist/components/index.js +35 -0
  109. package/dist/components/interaction-dialog-behavior.d.ts +15 -0
  110. package/dist/components/interaction-dialog-behavior.d.ts.map +1 -0
  111. package/dist/components/interaction-dialog-behavior.js +9 -0
  112. package/dist/components/surfaces/BlockerSurface.d.ts +27 -0
  113. package/dist/components/surfaces/BlockerSurface.d.ts.map +1 -0
  114. package/dist/components/surfaces/BlockerSurface.js +38 -0
  115. package/dist/components/surfaces/BoardSurface.d.ts +77 -0
  116. package/dist/components/surfaces/BoardSurface.d.ts.map +1 -0
  117. package/dist/components/surfaces/BoardSurface.js +180 -0
  118. package/dist/components/surfaces/ChromeSurface.d.ts +29 -0
  119. package/dist/components/surfaces/ChromeSurface.d.ts.map +1 -0
  120. package/dist/components/surfaces/ChromeSurface.js +34 -0
  121. package/dist/components/surfaces/ExhaustivenessAudit.d.ts +32 -0
  122. package/dist/components/surfaces/ExhaustivenessAudit.d.ts.map +1 -0
  123. package/dist/components/surfaces/ExhaustivenessAudit.js +65 -0
  124. package/dist/components/surfaces/InboxSurface.d.ts +40 -0
  125. package/dist/components/surfaces/InboxSurface.d.ts.map +1 -0
  126. package/dist/components/surfaces/InboxSurface.js +99 -0
  127. package/dist/components/surfaces/MarketSurface.d.ts +62 -0
  128. package/dist/components/surfaces/MarketSurface.d.ts.map +1 -0
  129. package/dist/components/surfaces/MarketSurface.js +242 -0
  130. package/dist/components/surfaces/PanelSurface.d.ts +111 -0
  131. package/dist/components/surfaces/PanelSurface.d.ts.map +1 -0
  132. package/dist/components/surfaces/PanelSurface.js +180 -0
  133. package/dist/components/surfaces/PlayerCardsSurface.d.ts +104 -0
  134. package/dist/components/surfaces/PlayerCardsSurface.d.ts.map +1 -0
  135. package/dist/components/surfaces/PlayerCardsSurface.js +178 -0
  136. package/dist/components/surfaces/internal/CardZoneFollowUpForm.d.ts +7 -0
  137. package/dist/components/surfaces/internal/CardZoneFollowUpForm.d.ts.map +1 -0
  138. package/dist/components/surfaces/internal/CardZoneFollowUpForm.js +9 -0
  139. package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts +71 -0
  140. package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -0
  141. package/dist/components/surfaces/internal/DefaultInteractionButton.js +82 -0
  142. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +21 -0
  143. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -0
  144. package/dist/components/surfaces/internal/useCardZoneInteractions.js +202 -0
  145. package/dist/components/surfaces/types.d.ts +59 -0
  146. package/dist/components/surfaces/types.d.ts.map +1 -0
  147. package/dist/components/surfaces/types.js +1 -0
  148. package/dist/context/ClientParamSchemaContext.d.ts +21 -0
  149. package/dist/context/ClientParamSchemaContext.d.ts.map +1 -0
  150. package/dist/context/ClientParamSchemaContext.js +12 -0
  151. package/dist/context/InteractionDraftContext.d.ts +69 -0
  152. package/dist/context/InteractionDraftContext.d.ts.map +1 -0
  153. package/dist/context/InteractionDraftContext.js +145 -0
  154. package/dist/context/PluginSessionContext.d.ts +33 -0
  155. package/dist/context/PluginSessionContext.d.ts.map +1 -0
  156. package/dist/context/PluginSessionContext.js +38 -0
  157. package/dist/context/PluginStateContext.d.ts +116 -0
  158. package/dist/context/PluginStateContext.d.ts.map +1 -0
  159. package/dist/context/PluginStateContext.js +186 -0
  160. package/dist/context/RuntimeContext.d.ts +49 -0
  161. package/dist/context/RuntimeContext.d.ts.map +1 -0
  162. package/dist/context/RuntimeContext.js +67 -0
  163. package/dist/defaults/components.d.ts +52 -0
  164. package/dist/defaults/components.d.ts.map +1 -0
  165. package/dist/defaults/components.js +159 -0
  166. package/dist/defaults/index.d.ts +2 -0
  167. package/dist/defaults/index.d.ts.map +1 -0
  168. package/dist/defaults/index.js +1 -0
  169. package/dist/errors/ValidationError.d.ts +10 -0
  170. package/dist/errors/ValidationError.d.ts.map +1 -0
  171. package/dist/errors/ValidationError.js +23 -0
  172. package/dist/helpers/cards.d.ts +3 -0
  173. package/dist/helpers/cards.d.ts.map +1 -0
  174. package/dist/helpers/cards.js +11 -0
  175. package/dist/helpers/track-board.d.ts +79 -0
  176. package/dist/helpers/track-board.d.ts.map +1 -0
  177. package/dist/helpers/track-board.js +56 -0
  178. package/dist/hooks/useActivePlayers.d.ts +16 -0
  179. package/dist/hooks/useActivePlayers.d.ts.map +1 -0
  180. package/dist/hooks/useActivePlayers.js +17 -0
  181. package/dist/hooks/useBoardInteractions.d.ts +110 -0
  182. package/dist/hooks/useBoardInteractions.d.ts.map +1 -0
  183. package/dist/hooks/useBoardInteractions.js +248 -0
  184. package/dist/hooks/useBoardTopology.d.ts +23 -0
  185. package/dist/hooks/useBoardTopology.d.ts.map +1 -0
  186. package/dist/hooks/useBoardTopology.js +128 -0
  187. package/dist/hooks/useCards.d.ts +3 -0
  188. package/dist/hooks/useCards.d.ts.map +1 -0
  189. package/dist/hooks/useCards.js +5 -0
  190. package/dist/hooks/useGameSelector.d.ts +13 -0
  191. package/dist/hooks/useGameSelector.d.ts.map +1 -0
  192. package/dist/hooks/useGameSelector.js +67 -0
  193. package/dist/hooks/useGameView.d.ts +6 -0
  194. package/dist/hooks/useGameView.d.ts.map +1 -0
  195. package/dist/hooks/useGameView.js +7 -0
  196. package/dist/hooks/useHandLayout.d.ts +120 -0
  197. package/dist/hooks/useHandLayout.d.ts.map +1 -0
  198. package/dist/hooks/useHandLayout.js +235 -0
  199. package/dist/hooks/useHexBoard.d.ts +19 -0
  200. package/dist/hooks/useHexBoard.d.ts.map +1 -0
  201. package/dist/hooks/useHexBoard.js +28 -0
  202. package/dist/hooks/useHexGrid.d.ts +56 -0
  203. package/dist/hooks/useHexGrid.d.ts.map +1 -0
  204. package/dist/hooks/useHexGrid.js +112 -0
  205. package/dist/hooks/useInteractionByKey.d.ts +29 -0
  206. package/dist/hooks/useInteractionByKey.d.ts.map +1 -0
  207. package/dist/hooks/useInteractionByKey.js +263 -0
  208. package/dist/hooks/useInteractionHandle.d.ts +103 -0
  209. package/dist/hooks/useInteractionHandle.d.ts.map +1 -0
  210. package/dist/hooks/useInteractionHandle.js +254 -0
  211. package/dist/hooks/useIsMobile.d.ts +7 -0
  212. package/dist/hooks/useIsMobile.d.ts.map +1 -0
  213. package/dist/hooks/useIsMobile.js +29 -0
  214. package/dist/hooks/useIsMyTurn.d.ts +6 -0
  215. package/dist/hooks/useIsMyTurn.d.ts.map +1 -0
  216. package/dist/hooks/useIsMyTurn.js +11 -0
  217. package/dist/hooks/useLobby.d.ts +28 -0
  218. package/dist/hooks/useLobby.d.ts.map +1 -0
  219. package/dist/hooks/useLobby.js +60 -0
  220. package/dist/hooks/useMe.d.ts +11 -0
  221. package/dist/hooks/useMe.d.ts.map +1 -0
  222. package/dist/hooks/useMe.js +32 -0
  223. package/dist/hooks/usePanZoom.d.ts +113 -0
  224. package/dist/hooks/usePanZoom.d.ts.map +1 -0
  225. package/dist/hooks/usePanZoom.js +165 -0
  226. package/dist/hooks/usePlayerInfo.d.ts +4 -0
  227. package/dist/hooks/usePlayerInfo.d.ts.map +1 -0
  228. package/dist/hooks/usePlayerInfo.js +21 -0
  229. package/dist/hooks/usePlayerTurnOrder.d.ts +15 -0
  230. package/dist/hooks/usePlayerTurnOrder.d.ts.map +1 -0
  231. package/dist/hooks/usePlayerTurnOrder.js +22 -0
  232. package/dist/hooks/usePluginRuntime.d.ts +45 -0
  233. package/dist/hooks/usePluginRuntime.d.ts.map +1 -0
  234. package/dist/hooks/usePluginRuntime.js +92 -0
  235. package/dist/hooks/useSeatInbox.d.ts +22 -0
  236. package/dist/hooks/useSeatInbox.d.ts.map +1 -0
  237. package/dist/hooks/useSeatInbox.js +43 -0
  238. package/dist/hooks/useSimultaneousPhase.d.ts +7 -0
  239. package/dist/hooks/useSimultaneousPhase.d.ts.map +1 -0
  240. package/dist/hooks/useSimultaneousPhase.js +8 -0
  241. package/dist/hooks/useSquareBoard.d.ts +21 -0
  242. package/dist/hooks/useSquareBoard.d.ts.map +1 -0
  243. package/dist/hooks/useSquareBoard.js +67 -0
  244. package/dist/hooks/useSquareGrid.d.ts +96 -0
  245. package/dist/hooks/useSquareGrid.d.ts.map +1 -0
  246. package/dist/hooks/useSquareGrid.js +152 -0
  247. package/dist/index.d.ts +30 -0
  248. package/dist/index.d.ts.map +1 -0
  249. package/dist/index.js +20 -0
  250. package/dist/internal/ui/alert.d.ts +8 -0
  251. package/dist/internal/ui/alert.d.ts.map +1 -0
  252. package/dist/internal/ui/alert.js +11 -0
  253. package/dist/internal/ui/button.d.ts +10 -0
  254. package/dist/internal/ui/button.d.ts.map +1 -0
  255. package/dist/internal/ui/button.js +21 -0
  256. package/dist/internal/ui/dialog.d.ts +16 -0
  257. package/dist/internal/ui/dialog.d.ts.map +1 -0
  258. package/dist/internal/ui/dialog.js +35 -0
  259. package/dist/internal/ui/input.d.ts +3 -0
  260. package/dist/internal/ui/input.d.ts.map +1 -0
  261. package/dist/internal/ui/input.js +5 -0
  262. package/dist/internal/ui/label.d.ts +4 -0
  263. package/dist/internal/ui/label.d.ts.map +1 -0
  264. package/dist/internal/ui/label.js +7 -0
  265. package/dist/internal/ui/select.d.ts +9 -0
  266. package/dist/internal/ui/select.d.ts.map +1 -0
  267. package/dist/internal/ui/select.js +23 -0
  268. package/dist/internal/ui/tooltip.d.ts +7 -0
  269. package/dist/internal/ui/tooltip.d.ts.map +1 -0
  270. package/dist/internal/ui/tooltip.js +16 -0
  271. package/dist/internal/ui/utils.d.ts +3 -0
  272. package/dist/internal/ui/utils.d.ts.map +1 -0
  273. package/dist/internal/ui/utils.js +4 -0
  274. package/dist/internal.d.ts +7 -0
  275. package/dist/internal.d.ts.map +1 -0
  276. package/dist/internal.js +4 -0
  277. package/dist/plugin-styles.css +246 -0
  278. package/dist/primitives/board.d.ts +29 -0
  279. package/dist/primitives/board.d.ts.map +1 -0
  280. package/dist/primitives/board.js +163 -0
  281. package/dist/primitives/game-ui-provider.d.ts +12 -0
  282. package/dist/primitives/game-ui-provider.d.ts.map +1 -0
  283. package/dist/primitives/game-ui-provider.js +7 -0
  284. package/dist/primitives/index.d.ts +8 -0
  285. package/dist/primitives/index.d.ts.map +1 -0
  286. package/dist/primitives/index.js +7 -0
  287. package/dist/primitives/interaction.d.ts +52 -0
  288. package/dist/primitives/interaction.d.ts.map +1 -0
  289. package/dist/primitives/interaction.js +250 -0
  290. package/dist/primitives/phase.d.ts +15 -0
  291. package/dist/primitives/phase.d.ts.map +1 -0
  292. package/dist/primitives/phase.js +18 -0
  293. package/dist/primitives/player-roster.d.ts +64 -0
  294. package/dist/primitives/player-roster.d.ts.map +1 -0
  295. package/dist/primitives/player-roster.js +149 -0
  296. package/dist/primitives/primitive-props.d.ts +15 -0
  297. package/dist/primitives/primitive-props.d.ts.map +1 -0
  298. package/dist/primitives/primitive-props.js +39 -0
  299. package/dist/primitives/prompt.d.ts +44 -0
  300. package/dist/primitives/prompt.d.ts.map +1 -0
  301. package/dist/primitives/prompt.js +101 -0
  302. package/dist/primitives/zone.d.ts +31 -0
  303. package/dist/primitives/zone.d.ts.map +1 -0
  304. package/dist/primitives/zone.js +58 -0
  305. package/dist/reducer.d.ts +21 -0
  306. package/dist/reducer.d.ts.map +1 -0
  307. package/dist/reducer.js +14 -0
  308. package/dist/runtime/createPluginRuntimeAPI.d.ts +67 -0
  309. package/dist/runtime/createPluginRuntimeAPI.d.ts.map +1 -0
  310. package/dist/runtime/createPluginRuntimeAPI.js +419 -0
  311. package/dist/theme/ThemeProvider.d.ts +98 -0
  312. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  313. package/dist/theme/ThemeProvider.js +148 -0
  314. package/dist/theme/board.d.ts +42 -0
  315. package/dist/theme/board.d.ts.map +1 -0
  316. package/dist/theme/board.js +34 -0
  317. package/dist/theme/css-vars.d.ts +31 -0
  318. package/dist/theme/css-vars.d.ts.map +1 -0
  319. package/dist/theme/css-vars.js +88 -0
  320. package/dist/theme/derive.d.ts +66 -0
  321. package/dist/theme/derive.d.ts.map +1 -0
  322. package/dist/theme/derive.js +161 -0
  323. package/dist/theme/index.d.ts +22 -0
  324. package/dist/theme/index.d.ts.map +1 -0
  325. package/dist/theme/index.js +20 -0
  326. package/dist/theme/presets/arcade.d.ts +10 -0
  327. package/dist/theme/presets/arcade.d.ts.map +1 -0
  328. package/dist/theme/presets/arcade.js +257 -0
  329. package/dist/theme/presets/studio.d.ts +10 -0
  330. package/dist/theme/presets/studio.d.ts.map +1 -0
  331. package/dist/theme/presets/studio.js +257 -0
  332. package/dist/theme/presets/tabletop.d.ts +15 -0
  333. package/dist/theme/presets/tabletop.d.ts.map +1 -0
  334. package/dist/theme/presets/tabletop.js +262 -0
  335. package/dist/theme/tokens.d.ts +345 -0
  336. package/dist/theme/tokens.d.ts.map +1 -0
  337. package/dist/theme/tokens.js +57 -0
  338. package/dist/types/player-state.d.ts +337 -0
  339. package/dist/types/player-state.d.ts.map +1 -0
  340. package/dist/types/player-state.js +1 -0
  341. package/dist/types/plugin-state.d.ts +324 -0
  342. package/dist/types/plugin-state.d.ts.map +1 -0
  343. package/dist/types/plugin-state.js +1 -0
  344. package/dist/types/reducer-state.d.ts +10 -0
  345. package/dist/types/reducer-state.d.ts.map +1 -0
  346. package/dist/types/reducer-state.js +1 -0
  347. package/dist/types/runtime-api.d.ts +99 -0
  348. package/dist/types/runtime-api.d.ts.map +1 -0
  349. package/dist/types/runtime-api.js +1 -0
  350. package/dist/types/tiled-board.d.ts +187 -0
  351. package/dist/types/tiled-board.d.ts.map +1 -0
  352. package/dist/types/tiled-board.js +226 -0
  353. package/dist/ui-contract.d.ts +78 -0
  354. package/dist/ui-contract.d.ts.map +1 -0
  355. package/dist/ui-contract.js +15 -0
  356. package/dist/ui-sdk.d.ts +3409 -0
  357. package/dist/utils/interaction-inputs.d.ts +22 -0
  358. package/dist/utils/interaction-inputs.d.ts.map +1 -0
  359. package/dist/utils/interaction-inputs.js +219 -0
  360. package/dist/utils/interaction-labels.d.ts +4 -0
  361. package/dist/utils/interaction-labels.d.ts.map +1 -0
  362. package/dist/utils/interaction-labels.js +18 -0
  363. package/dist/utils/interaction-status.d.ts +15 -0
  364. package/dist/utils/interaction-status.d.ts.map +1 -0
  365. package/dist/utils/interaction-status.js +31 -0
  366. package/package.json +101 -0
  367. package/src/components/ActionButton.tsx +48 -0
  368. package/src/components/ActionPanel.tsx +310 -0
  369. package/src/components/Card.tsx +385 -0
  370. package/src/components/ChromeSuppressionContext.tsx +70 -0
  371. package/src/components/CostDisplay.test.tsx +23 -0
  372. package/src/components/CostDisplay.tsx +145 -0
  373. package/src/components/DiceRoller.tsx +601 -0
  374. package/src/components/Drawer.tsx +179 -0
  375. package/src/components/ErrorBoundary.tsx +119 -0
  376. package/src/components/GameEndDisplay.test.tsx +19 -0
  377. package/src/components/GameEndDisplay.tsx +398 -0
  378. package/src/components/GameSkeleton.tsx +260 -0
  379. package/src/components/Hand.tsx +387 -0
  380. package/src/components/HandDock.tsx +257 -0
  381. package/src/components/InteractionForm.test.tsx +303 -0
  382. package/src/components/InteractionForm.tsx +1029 -0
  383. package/src/components/MoreActions.test.tsx +93 -0
  384. package/src/components/MoreActions.tsx +143 -0
  385. package/src/components/PhaseIndicator.tsx +341 -0
  386. package/src/components/PlayArea.tsx +125 -0
  387. package/src/components/PluginRuntime.tsx +92 -0
  388. package/src/components/PrimaryActionButton.test.tsx +138 -0
  389. package/src/components/PrimaryActionButton.tsx +351 -0
  390. package/src/components/PrimaryButton.tsx +44 -0
  391. package/src/components/PromptDialogHost.tsx +92 -0
  392. package/src/components/ResourceCounter.test.tsx +29 -0
  393. package/src/components/ResourceCounter.tsx +275 -0
  394. package/src/components/ThemedButton.tsx +78 -0
  395. package/src/components/Toast.tsx +251 -0
  396. package/src/components/__fixtures__/ActionButton.fixture.tsx +234 -0
  397. package/src/components/__fixtures__/ActionPanel.fixture.tsx +298 -0
  398. package/src/components/__fixtures__/Card.fixture.tsx +185 -0
  399. package/src/components/__fixtures__/CostDisplay.fixture.tsx +156 -0
  400. package/src/components/__fixtures__/DiceRoller.fixture.tsx +435 -0
  401. package/src/components/__fixtures__/Drawer.fixture.tsx +113 -0
  402. package/src/components/__fixtures__/ErrorBoundary.fixture.tsx +82 -0
  403. package/src/components/__fixtures__/GameEndDisplay.fixture.tsx +188 -0
  404. package/src/components/__fixtures__/GameSkeleton.fixture.tsx +46 -0
  405. package/src/components/__fixtures__/Hand.fixture.tsx +522 -0
  406. package/src/components/__fixtures__/HexGrid.fixture.tsx +1181 -0
  407. package/src/components/__fixtures__/NetworkGraph.fixture.tsx +599 -0
  408. package/src/components/__fixtures__/PhaseIndicator.fixture.tsx +181 -0
  409. package/src/components/__fixtures__/PlayArea.fixture.tsx +221 -0
  410. package/src/components/__fixtures__/ResourceCounter.fixture.tsx +227 -0
  411. package/src/components/__fixtures__/SlotSystem.fixture.tsx +824 -0
  412. package/src/components/__fixtures__/SquareGrid.fixture.tsx +764 -0
  413. package/src/components/__fixtures__/Toast.fixture.tsx +97 -0
  414. package/src/components/__fixtures__/TrackBoard.fixture.tsx +685 -0
  415. package/src/components/__fixtures__/ZoneMap.fixture.tsx +754 -0
  416. package/src/components/board/HexGrid.tsx +1294 -0
  417. package/src/components/board/NetworkGraph.tsx +476 -0
  418. package/src/components/board/SlotSystem.tsx +339 -0
  419. package/src/components/board/SquareGrid.tsx +1165 -0
  420. package/src/components/board/TrackBoard.tsx +496 -0
  421. package/src/components/board/ZoneMap.tsx +448 -0
  422. package/src/components/board/hex-board-view.test.tsx +114 -0
  423. package/src/components/board/hex-board-view.ts +123 -0
  424. package/src/components/board/index.ts +142 -0
  425. package/src/components/board/interaction-accessibility.ts +21 -0
  426. package/src/components/board/target-layer-grids.test.tsx +420 -0
  427. package/src/components/board/target-layer.ts +30 -0
  428. package/src/components/card-render-content.type-test.ts +27 -0
  429. package/src/components/index.ts +208 -0
  430. package/src/components/interaction-dialog-behavior.test.ts +23 -0
  431. package/src/components/interaction-dialog-behavior.ts +22 -0
  432. package/src/components/surfaces/BlockerSurface.test.tsx +158 -0
  433. package/src/components/surfaces/BlockerSurface.tsx +127 -0
  434. package/src/components/surfaces/BoardSurface.tsx +340 -0
  435. package/src/components/surfaces/ChromeSurface.tsx +123 -0
  436. package/src/components/surfaces/ExhaustivenessAudit.tsx +91 -0
  437. package/src/components/surfaces/InboxSurface.test.tsx +149 -0
  438. package/src/components/surfaces/InboxSurface.tsx +245 -0
  439. package/src/components/surfaces/MarketSurface.tsx +544 -0
  440. package/src/components/surfaces/PanelSurface.test.tsx +496 -0
  441. package/src/components/surfaces/PanelSurface.tsx +458 -0
  442. package/src/components/surfaces/PlayerCardsSurface.tsx +525 -0
  443. package/src/components/surfaces/internal/CardZoneFollowUpForm.tsx +35 -0
  444. package/src/components/surfaces/internal/DefaultInteractionButton.tsx +219 -0
  445. package/src/components/surfaces/internal/useCardZoneInteractions.ts +311 -0
  446. package/src/components/surfaces/types.ts +100 -0
  447. package/src/context/ClientParamSchemaContext.tsx +44 -0
  448. package/src/context/InteractionDraftContext.tsx +204 -0
  449. package/src/context/PluginSessionContext.tsx +47 -0
  450. package/src/context/PluginStateContext.tsx +254 -0
  451. package/src/context/RuntimeContext.tsx +96 -0
  452. package/src/defaults/components.tsx +442 -0
  453. package/src/defaults/defaults.test.tsx +230 -0
  454. package/src/defaults/index.ts +1 -0
  455. package/src/errors/ValidationError.ts +29 -0
  456. package/src/helpers/cards.ts +19 -0
  457. package/src/helpers/track-board.ts +211 -0
  458. package/src/hooks/useActivePlayers.ts +19 -0
  459. package/src/hooks/useBoardInteractions.test.tsx +622 -0
  460. package/src/hooks/useBoardInteractions.ts +434 -0
  461. package/src/hooks/useBoardTopology.ts +316 -0
  462. package/src/hooks/useCards.test.tsx +129 -0
  463. package/src/hooks/useCards.ts +10 -0
  464. package/src/hooks/useGameSelector.ts +105 -0
  465. package/src/hooks/useGameView.ts +9 -0
  466. package/src/hooks/useHandLayout.ts +349 -0
  467. package/src/hooks/useHexBoard.ts +74 -0
  468. package/src/hooks/useHexGrid.ts +185 -0
  469. package/src/hooks/useInteractionByKey.ts +349 -0
  470. package/src/hooks/useInteractionHandle.ts +437 -0
  471. package/src/hooks/useIsMobile.ts +35 -0
  472. package/src/hooks/useIsMyTurn.test.tsx +99 -0
  473. package/src/hooks/useIsMyTurn.ts +15 -0
  474. package/src/hooks/useLobby.ts +76 -0
  475. package/src/hooks/useMe.ts +48 -0
  476. package/src/hooks/usePanZoom.ts +278 -0
  477. package/src/hooks/usePlayerInfo.ts +28 -0
  478. package/src/hooks/usePlayerTurnOrder.ts +23 -0
  479. package/src/hooks/usePluginRuntime.test.tsx +102 -0
  480. package/src/hooks/usePluginRuntime.ts +130 -0
  481. package/src/hooks/useSeatInbox.ts +61 -0
  482. package/src/hooks/useSimultaneousPhase.ts +10 -0
  483. package/src/hooks/useSquareBoard.ts +124 -0
  484. package/src/hooks/useSquareGrid.ts +328 -0
  485. package/src/index.test.ts +474 -0
  486. package/src/index.ts +148 -0
  487. package/src/internal/ui/alert.tsx +51 -0
  488. package/src/internal/ui/button.tsx +58 -0
  489. package/src/internal/ui/dialog.tsx +134 -0
  490. package/src/internal/ui/input.tsx +21 -0
  491. package/src/internal/ui/label.tsx +21 -0
  492. package/src/internal/ui/select.tsx +129 -0
  493. package/src/internal/ui/tooltip.tsx +54 -0
  494. package/src/internal/ui/utils.ts +5 -0
  495. package/src/internal.ts +18 -0
  496. package/src/plugin-styles.css +246 -0
  497. package/src/primitives/board.test.tsx +139 -0
  498. package/src/primitives/board.tsx +267 -0
  499. package/src/primitives/game-ui-provider.tsx +35 -0
  500. package/src/primitives/index.ts +83 -0
  501. package/src/primitives/interaction.test.tsx +420 -0
  502. package/src/primitives/interaction.tsx +405 -0
  503. package/src/primitives/phase.test.tsx +82 -0
  504. package/src/primitives/phase.tsx +43 -0
  505. package/src/primitives/player-roster.test.tsx +168 -0
  506. package/src/primitives/player-roster.tsx +301 -0
  507. package/src/primitives/primitive-props.tsx +82 -0
  508. package/src/primitives/prompt.test.tsx +159 -0
  509. package/src/primitives/prompt.tsx +203 -0
  510. package/src/primitives/zone.tsx +113 -0
  511. package/src/reducer.ts +42 -0
  512. package/src/runtime/createPluginRuntimeAPI.ts +605 -0
  513. package/src/theme/ThemeProvider.test.tsx +36 -0
  514. package/src/theme/ThemeProvider.tsx +252 -0
  515. package/src/theme/board.ts +61 -0
  516. package/src/theme/css-vars.ts +105 -0
  517. package/src/theme/derive.ts +240 -0
  518. package/src/theme/index.ts +61 -0
  519. package/src/theme/presets/arcade.ts +261 -0
  520. package/src/theme/presets/studio.ts +261 -0
  521. package/src/theme/presets/tabletop.ts +266 -0
  522. package/src/theme/theme.test.ts +258 -0
  523. package/src/theme/tokens.ts +392 -0
  524. package/src/types/player-state.ts +445 -0
  525. package/src/types/plugin-state.ts +407 -0
  526. package/src/types/reducer-state.ts +24 -0
  527. package/src/types/runtime-api.ts +114 -0
  528. package/src/types/tiled-board.ts +785 -0
  529. package/src/ui-contract.ts +168 -0
  530. package/src/utils/interaction-inputs.test.ts +109 -0
  531. package/src/utils/interaction-inputs.ts +331 -0
  532. package/src/utils/interaction-labels.ts +23 -0
  533. package/src/utils/interaction-status.ts +59 -0
@@ -0,0 +1,1029 @@
1
+ import {
2
+ useEffect,
3
+ useId,
4
+ useMemo,
5
+ useState,
6
+ type CSSProperties,
7
+ type FormEvent,
8
+ type ReactNode,
9
+ } from "react";
10
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
11
+ import { Input } from "../internal/ui/input.js";
12
+ import { Label } from "../internal/ui/label.js";
13
+ import {
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ } from "../internal/ui/select.js";
19
+ import { surfaceStyle } from "../theme/derive.js";
20
+ import { useTheme, useThemeCssVars } from "../theme/ThemeProvider.js";
21
+ import type {
22
+ DraftValidation,
23
+ InteractionHandle,
24
+ InteractionParamsShape,
25
+ } from "../hooks/useInteractionHandle.js";
26
+ import type {
27
+ InteractionChoiceOption,
28
+ InteractionDescriptor,
29
+ InteractionInputDescriptor,
30
+ InputDomain,
31
+ } from "../types/plugin-state.js";
32
+ import { interactionLabel } from "../utils/interaction-labels.js";
33
+ import { useChromeSuppression } from "./ChromeSuppressionContext.js";
34
+ import { ThemedButton } from "./ThemedButton.js";
35
+
36
+ export interface InteractionFieldRenderProps<
37
+ Params extends InteractionParamsShape = InteractionParamsShape,
38
+ Key extends keyof Params & string = keyof Params & string,
39
+ > {
40
+ descriptor: InteractionDescriptor;
41
+ input: InteractionInputDescriptor & { key: Key };
42
+ handle: InteractionHandle<Params>;
43
+ value: Params[Key] | undefined;
44
+ setValue: (value: Params[Key]) => void;
45
+ clearValue: () => void;
46
+ errors: readonly string[];
47
+ missing: boolean;
48
+ disabled: boolean;
49
+ }
50
+
51
+ export type InteractionFieldRenderMap<
52
+ Params extends InteractionParamsShape = InteractionParamsShape,
53
+ > = Partial<{
54
+ [K in keyof Params & string]: (
55
+ props: InteractionFieldRenderProps<Params, K>,
56
+ ) => ReactNode;
57
+ }>;
58
+
59
+ export interface InteractionFieldProps<
60
+ Params extends InteractionParamsShape = InteractionParamsShape,
61
+ Key extends keyof Params & string = keyof Params & string,
62
+ > {
63
+ descriptor: InteractionDescriptor;
64
+ inputKey: Key;
65
+ handle: InteractionHandle<Params>;
66
+ errors?: readonly string[];
67
+ missing?: boolean;
68
+ disabled?: boolean;
69
+ render?: InteractionFieldRenderMap<Params>[Key];
70
+ }
71
+
72
+ export interface InteractionFormProps<
73
+ Params extends InteractionParamsShape = InteractionParamsShape,
74
+ DefaultedKeys extends keyof Params & string = never,
75
+ > {
76
+ descriptor: InteractionDescriptor;
77
+ handle: InteractionHandle<Params, DefaultedKeys>;
78
+ fields?: ReadonlyArray<keyof Params & string>;
79
+ hiddenFields?: ReadonlyArray<keyof Params & string>;
80
+ renderFields?: InteractionFieldRenderMap<Params>;
81
+ title?: ReactNode;
82
+ description?: ReactNode;
83
+ submitLabel?: ReactNode;
84
+ cancelLabel?: ReactNode;
85
+ onCancel?: () => void;
86
+ onSubmitSuccess?: () => void;
87
+ disabled?: boolean;
88
+ accordion?: boolean;
89
+ defaultOpen?: boolean;
90
+ style?: CSSProperties;
91
+ }
92
+
93
+ const EMPTY_FIELD_ERRORS: readonly string[] = [];
94
+
95
+ export function InteractionForm<
96
+ Params extends InteractionParamsShape = InteractionParamsShape,
97
+ DefaultedKeys extends keyof Params & string = never,
98
+ >({
99
+ descriptor,
100
+ handle,
101
+ fields,
102
+ hiddenFields,
103
+ renderFields,
104
+ title,
105
+ description,
106
+ submitLabel,
107
+ cancelLabel = "Cancel",
108
+ onCancel,
109
+ onSubmitSuccess,
110
+ disabled = false,
111
+ accordion = true,
112
+ defaultOpen = false,
113
+ style,
114
+ }: InteractionFormProps<Params, DefaultedKeys>) {
115
+ const theme = useTheme();
116
+ const fallbackLabel = interactionLabel(descriptor);
117
+ const formId = useId();
118
+ useChromeSuppression(formId, true);
119
+ const [pending, setPending] = useState(false);
120
+ const [validation, setValidation] = useState<DraftValidation<Params> | null>(
121
+ null,
122
+ );
123
+ const [formError, setFormError] = useState<string | null>(null);
124
+ const [accordionOpen, setAccordionOpen] = useState(defaultOpen);
125
+ const hidden = useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
126
+ const visibleInputs = useMemo(() => {
127
+ const allowed = fields ? new Set(fields) : null;
128
+ return defaultFormInputs(descriptor).filter((input) => {
129
+ const key = input.key as keyof Params & string;
130
+ if (allowed && !allowed.has(key)) return false;
131
+ return !hidden.has(key);
132
+ });
133
+ }, [descriptor, fields, hidden]);
134
+
135
+ const currentValidation = validation;
136
+ const fieldErrors = (currentValidation?.fieldErrors ?? {}) as Partial<
137
+ Record<keyof Params & string, readonly string[]>
138
+ >;
139
+ const missing = new Set<string>(currentValidation?.missing ?? []);
140
+ const formErrors = [
141
+ ...(currentValidation?.formErrors ?? []),
142
+ ...(formError ? [formError] : []),
143
+ ];
144
+ const isDisabled = disabled || pending || !descriptor.available;
145
+ const useAccordion = accordion && visibleInputs.length > 0;
146
+
147
+ useEffect(() => {
148
+ setAccordionOpen(defaultOpen);
149
+ }, [defaultOpen, descriptor.interactionId]);
150
+
151
+ const containerStyle: CSSProperties = {
152
+ ...surfaceStyle(theme, { tone: "card" }),
153
+ display: "flex",
154
+ flexDirection: "column",
155
+ gap: theme.space[2],
156
+ padding: theme.space[3],
157
+ minWidth: "min(100%, 280px)",
158
+ boxSizing: "border-box",
159
+ fontFamily: theme.typography.fontFamily.body,
160
+ ...style,
161
+ };
162
+
163
+ const submit = async (event: FormEvent) => {
164
+ event.preventDefault();
165
+ if (isDisabled) return;
166
+ const nextValidation = handle.validateDraft();
167
+ setValidation(nextValidation);
168
+ setFormError(null);
169
+ if (!nextValidation.ok) {
170
+ setAccordionOpen(true);
171
+ return;
172
+ }
173
+ setPending(true);
174
+ try {
175
+ await handle.submitDraft();
176
+ onSubmitSuccess?.();
177
+ setAccordionOpen(defaultOpen);
178
+ } catch (error) {
179
+ setAccordionOpen(true);
180
+ setFormError(
181
+ error instanceof Error
182
+ ? error.message
183
+ : "Interaction submission failed",
184
+ );
185
+ } finally {
186
+ setPending(false);
187
+ }
188
+ };
189
+
190
+ const header = (
191
+ <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
192
+ <strong
193
+ style={{
194
+ fontFamily: theme.typography.fontFamily.display,
195
+ fontSize: theme.typography.fontSize.md,
196
+ color: theme.semantic.text.primary,
197
+ }}
198
+ >
199
+ {title ?? fallbackLabel}
200
+ </strong>
201
+ {description ? (
202
+ <span
203
+ style={{
204
+ fontSize: theme.typography.fontSize.sm,
205
+ color: theme.semantic.text.muted,
206
+ }}
207
+ >
208
+ {description}
209
+ </span>
210
+ ) : null}
211
+ </div>
212
+ );
213
+
214
+ const fieldsContent =
215
+ visibleInputs.length > 0 ? (
216
+ <div
217
+ style={{
218
+ display: "flex",
219
+ flexDirection: "column",
220
+ gap: theme.space[3],
221
+ }}
222
+ >
223
+ {visibleInputs.map((input) => {
224
+ const key = input.key as keyof Params & string;
225
+ return (
226
+ <InteractionField<Params>
227
+ key={input.key}
228
+ descriptor={descriptor}
229
+ inputKey={key}
230
+ handle={handle}
231
+ errors={fieldErrors[key] ?? []}
232
+ missing={missing.has(key)}
233
+ disabled={isDisabled}
234
+ render={renderFields?.[key]}
235
+ />
236
+ );
237
+ })}
238
+ </div>
239
+ ) : (
240
+ <span
241
+ style={{
242
+ fontSize: theme.typography.fontSize.sm,
243
+ color: theme.semantic.text.muted,
244
+ }}
245
+ >
246
+ No visible fields are required. Board or card selection may complete
247
+ this interaction.
248
+ </span>
249
+ );
250
+
251
+ const formErrorContent =
252
+ formErrors.length > 0 ? (
253
+ <div
254
+ role="alert"
255
+ style={{
256
+ display: "flex",
257
+ flexDirection: "column",
258
+ gap: theme.space[1],
259
+ color: theme.semantic.intent.danger.solid,
260
+ fontSize: theme.typography.fontSize.sm,
261
+ }}
262
+ >
263
+ {formErrors.map((error) => (
264
+ <span key={error}>{error}</span>
265
+ ))}
266
+ </div>
267
+ ) : null;
268
+
269
+ const actions = (
270
+ <div
271
+ style={{
272
+ display: "flex",
273
+ gap: theme.space[2],
274
+ justifyContent: "flex-end",
275
+ }}
276
+ >
277
+ {onCancel ? (
278
+ <ThemedButton
279
+ type="button"
280
+ variant="secondary"
281
+ size="sm"
282
+ disabled={pending}
283
+ onClick={onCancel}
284
+ className="h-9 px-3 text-sm"
285
+ >
286
+ {cancelLabel}
287
+ </ThemedButton>
288
+ ) : null}
289
+ <ThemedButton
290
+ type="submit"
291
+ variant="primary"
292
+ size="sm"
293
+ disabled={isDisabled}
294
+ className="h-9 px-3 text-sm"
295
+ >
296
+ {pending ? "Submitting..." : (submitLabel ?? fallbackLabel)}
297
+ </ThemedButton>
298
+ </div>
299
+ );
300
+
301
+ const body = (
302
+ <>
303
+ {fieldsContent}
304
+ {formErrorContent}
305
+ {actions}
306
+ </>
307
+ );
308
+
309
+ return (
310
+ <form
311
+ data-interaction-form
312
+ data-interaction-id={descriptor.interactionId}
313
+ onSubmit={(event) => void submit(event)}
314
+ style={containerStyle}
315
+ >
316
+ {useAccordion ? (
317
+ <AccordionPrimitive.Root
318
+ type="single"
319
+ collapsible
320
+ value={accordionOpen ? "fields" : undefined}
321
+ onValueChange={(value: string) =>
322
+ setAccordionOpen(value === "fields")
323
+ }
324
+ >
325
+ <AccordionPrimitive.Item value="fields">
326
+ <AccordionPrimitive.Header style={{ margin: 0 }}>
327
+ <AccordionPrimitive.Trigger
328
+ style={{
329
+ alignItems: "center",
330
+ appearance: "none",
331
+ background: "transparent",
332
+ border: 0,
333
+ color: theme.semantic.text.primary,
334
+ cursor: "pointer",
335
+ display: "flex",
336
+ fontFamily: theme.typography.fontFamily.display,
337
+ fontSize: theme.typography.fontSize.md,
338
+ fontWeight: theme.typography.fontWeight.bold,
339
+ justifyContent: "space-between",
340
+ padding: 0,
341
+ textAlign: "left",
342
+ width: "100%",
343
+ }}
344
+ >
345
+ <span>{title ?? fallbackLabel}</span>
346
+ <span aria-hidden>{accordionOpen ? "−" : "+"}</span>
347
+ </AccordionPrimitive.Trigger>
348
+ </AccordionPrimitive.Header>
349
+ <AccordionPrimitive.Content>
350
+ <div
351
+ style={{
352
+ display: "flex",
353
+ flexDirection: "column",
354
+ gap: theme.space[2],
355
+ marginTop: theme.space[2],
356
+ }}
357
+ >
358
+ {description ? (
359
+ <span
360
+ style={{
361
+ fontSize: theme.typography.fontSize.sm,
362
+ color: theme.semantic.text.muted,
363
+ }}
364
+ >
365
+ {description}
366
+ </span>
367
+ ) : null}
368
+ {body}
369
+ </div>
370
+ </AccordionPrimitive.Content>
371
+ </AccordionPrimitive.Item>
372
+ </AccordionPrimitive.Root>
373
+ ) : (
374
+ <>
375
+ {header}
376
+ {body}
377
+ </>
378
+ )}
379
+ </form>
380
+ );
381
+ }
382
+
383
+ export function InteractionField<
384
+ Params extends InteractionParamsShape = InteractionParamsShape,
385
+ Key extends keyof Params & string = keyof Params & string,
386
+ >({
387
+ descriptor,
388
+ inputKey,
389
+ handle,
390
+ errors = EMPTY_FIELD_ERRORS,
391
+ missing = false,
392
+ disabled = false,
393
+ render,
394
+ }: InteractionFieldProps<Params, Key>) {
395
+ const input = descriptor.inputs.find(
396
+ (candidate) => candidate.key === inputKey,
397
+ );
398
+ if (!input) return null;
399
+ const typedInput = input as InteractionInputDescriptor & { key: Key };
400
+ const value = handle.values[inputKey] as Params[Key] | undefined;
401
+ const props: InteractionFieldRenderProps<Params, Key> = {
402
+ descriptor,
403
+ input: typedInput,
404
+ handle,
405
+ value,
406
+ setValue: (next) => handle.setInput(inputKey, next),
407
+ clearValue: () => handle.clearInput(inputKey),
408
+ errors,
409
+ missing,
410
+ disabled,
411
+ };
412
+ if (render) return <>{render(props)}</>;
413
+ return <DefaultInteractionField {...props} />;
414
+ }
415
+
416
+ export function hasDefaultInteractionFormFields(
417
+ descriptor: Pick<InteractionDescriptor, "inputs">,
418
+ ): boolean {
419
+ return defaultFormInputs(descriptor).length > 0;
420
+ }
421
+
422
+ export function defaultFormInputs(
423
+ descriptor: Pick<InteractionDescriptor, "inputs">,
424
+ ): InteractionInputDescriptor[] {
425
+ return descriptor.inputs.filter((input) => {
426
+ switch (input.domain.type) {
427
+ case "choice":
428
+ case "choiceList":
429
+ case "resourceMap":
430
+ case "boundedNumber":
431
+ return true;
432
+ case "target":
433
+ return input.domain.selection?.mode === "many";
434
+ }
435
+ });
436
+ }
437
+
438
+ function DefaultInteractionField<
439
+ Params extends InteractionParamsShape,
440
+ Key extends keyof Params & string,
441
+ >(props: InteractionFieldRenderProps<Params, Key>) {
442
+ const { input } = props;
443
+ switch (input.domain.type) {
444
+ case "choice":
445
+ if (input.domain.selection?.mode === "many") {
446
+ return (
447
+ <ChoiceListField
448
+ {...props}
449
+ domain={{
450
+ type: "choiceList",
451
+ choices: input.domain.choices,
452
+ min: input.domain.selection.min ?? 0,
453
+ max: input.domain.selection.max,
454
+ selection: input.domain.selection,
455
+ }}
456
+ />
457
+ );
458
+ }
459
+ return <ChoiceField {...props} domain={input.domain} />;
460
+ case "choiceList":
461
+ return <ChoiceListField {...props} domain={input.domain} />;
462
+ case "resourceMap":
463
+ return <ResourceMapField {...props} domain={input.domain} />;
464
+ case "boundedNumber":
465
+ return <BoundedNumberField {...props} domain={input.domain} />;
466
+ case "target":
467
+ return <TargetSummaryField {...props} domain={input.domain} />;
468
+ }
469
+ }
470
+
471
+ function FieldFrame({
472
+ label,
473
+ controlId,
474
+ errors,
475
+ missing,
476
+ children,
477
+ }: {
478
+ label: ReactNode;
479
+ controlId?: string;
480
+ errors: readonly string[];
481
+ missing: boolean;
482
+ children: ReactNode;
483
+ }) {
484
+ const theme = useTheme();
485
+ const messages = errors.length > 0 ? errors : missing ? ["Required"] : [];
486
+ return (
487
+ <div
488
+ style={{
489
+ display: "flex",
490
+ flexDirection: "column",
491
+ gap: theme.space[1],
492
+ fontSize: theme.typography.fontSize.sm,
493
+ color: theme.semantic.text.primary,
494
+ }}
495
+ >
496
+ <Label
497
+ htmlFor={controlId}
498
+ style={{
499
+ color: theme.semantic.text.primary,
500
+ fontWeight: theme.typography.fontWeight.bold,
501
+ }}
502
+ >
503
+ {label}
504
+ </Label>
505
+ {children}
506
+ {messages.length > 0 ? (
507
+ <span
508
+ role="alert"
509
+ style={{
510
+ display: "flex",
511
+ flexDirection: "column",
512
+ gap: 2,
513
+ color: theme.semantic.intent.danger.solid,
514
+ fontSize: theme.typography.fontSize.xs,
515
+ }}
516
+ >
517
+ {messages.map((message) => (
518
+ <span key={message}>{message}</span>
519
+ ))}
520
+ </span>
521
+ ) : null}
522
+ </div>
523
+ );
524
+ }
525
+
526
+ function ChoiceOptionLabel({ choice }: { choice: InteractionChoiceOption }) {
527
+ const theme = useTheme();
528
+ return (
529
+ <span
530
+ style={{
531
+ display: "inline-flex",
532
+ alignItems: "center",
533
+ gap: 6,
534
+ minWidth: 0,
535
+ }}
536
+ >
537
+ <span
538
+ style={{
539
+ display: "inline-flex",
540
+ alignItems: "center",
541
+ gap: 6,
542
+ minWidth: 0,
543
+ }}
544
+ >
545
+ {choice.icon ? (
546
+ <span aria-hidden style={{ fontSize: "1.1em", lineHeight: 1 }}>
547
+ {choice.icon}
548
+ </span>
549
+ ) : null}
550
+ <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
551
+ {choice.label}
552
+ </span>
553
+ </span>
554
+ {choice.badge ? (
555
+ <span
556
+ style={{
557
+ borderRadius: 999,
558
+ background: theme.semantic.surface.inset,
559
+ color: theme.semantic.text.muted,
560
+ fontSize: theme.typography.fontSize.xs,
561
+ fontWeight: theme.typography.fontWeight.bold,
562
+ lineHeight: 1,
563
+ padding: "3px 6px",
564
+ whiteSpace: "nowrap",
565
+ }}
566
+ >
567
+ {choice.badge}
568
+ </span>
569
+ ) : null}
570
+ </span>
571
+ );
572
+ }
573
+
574
+ function ChoiceDescription({ choice }: { choice?: InteractionChoiceOption }) {
575
+ const theme = useTheme();
576
+ const message = choice?.disabledReason ?? choice?.description;
577
+ if (!message) return null;
578
+ return (
579
+ <span
580
+ style={{
581
+ color: choice?.disabledReason
582
+ ? theme.semantic.intent.danger.solid
583
+ : theme.semantic.text.muted,
584
+ fontSize: theme.typography.fontSize.xs,
585
+ }}
586
+ >
587
+ {message}
588
+ </span>
589
+ );
590
+ }
591
+
592
+ function ChoiceField<
593
+ Params extends InteractionParamsShape,
594
+ Key extends keyof Params & string,
595
+ >({
596
+ input,
597
+ value,
598
+ setValue,
599
+ errors,
600
+ missing,
601
+ disabled,
602
+ domain,
603
+ }: InteractionFieldRenderProps<Params, Key> & {
604
+ domain: InputDomain;
605
+ }) {
606
+ const theme = useTheme();
607
+ const themeCssVars = useThemeCssVars();
608
+ const choices = domain.choices ?? [];
609
+ const controlId = useId();
610
+ const selectedChoice =
611
+ typeof value === "string"
612
+ ? choices.find((choice) => choice.value === value)
613
+ : undefined;
614
+ if (choices.length > 0 && choices.length <= 3) {
615
+ return (
616
+ <FieldFrame
617
+ label={labelForInput(input)}
618
+ errors={errors}
619
+ missing={missing}
620
+ >
621
+ <span
622
+ style={{
623
+ display: "inline-flex",
624
+ flexWrap: "wrap",
625
+ gap: theme.space[1],
626
+ }}
627
+ >
628
+ {choices.map((choice) => {
629
+ const selected = value === choice.value;
630
+ return (
631
+ <ThemedButton
632
+ key={choice.value}
633
+ type="button"
634
+ variant={selected ? "primary" : "secondary"}
635
+ size="sm"
636
+ disabled={disabled || choice.disabled}
637
+ aria-pressed={selected}
638
+ title={choice.disabledReason ?? choice.description}
639
+ onClick={() => setValue(choice.value as Params[Key])}
640
+ className="h-8 px-3 text-sm"
641
+ >
642
+ <ChoiceOptionLabel choice={choice} />
643
+ </ThemedButton>
644
+ );
645
+ })}
646
+ </span>
647
+ </FieldFrame>
648
+ );
649
+ }
650
+ return (
651
+ <FieldFrame
652
+ label={labelForInput(input)}
653
+ controlId={controlId}
654
+ errors={errors}
655
+ missing={missing}
656
+ >
657
+ <Select
658
+ disabled={disabled}
659
+ value={typeof value === "string" ? value : undefined}
660
+ onValueChange={(next: string) => setValue(next as Params[Key])}
661
+ >
662
+ <SelectTrigger id={controlId} size="sm" className="w-full bg-white">
663
+ <span data-slot="select-value">
664
+ {selectedChoice ? (
665
+ <ChoiceOptionLabel choice={selectedChoice} />
666
+ ) : (
667
+ <span style={{ color: theme.semantic.text.muted }}>
668
+ Choose...
669
+ </span>
670
+ )}
671
+ </span>
672
+ </SelectTrigger>
673
+ <SelectContent
674
+ style={{
675
+ ...themeCssVars,
676
+ fontFamily: theme.typography.fontFamily.body,
677
+ }}
678
+ >
679
+ {choices.map((choice) => (
680
+ <SelectItem
681
+ key={choice.value}
682
+ value={choice.value}
683
+ textValue={choice.label}
684
+ disabled={choice.disabled}
685
+ >
686
+ <ChoiceOptionLabel choice={choice} />
687
+ </SelectItem>
688
+ ))}
689
+ </SelectContent>
690
+ </Select>
691
+ <ChoiceDescription choice={selectedChoice} />
692
+ </FieldFrame>
693
+ );
694
+ }
695
+
696
+ function ChoiceListField<
697
+ Params extends InteractionParamsShape,
698
+ Key extends keyof Params & string,
699
+ >({
700
+ input,
701
+ value,
702
+ setValue,
703
+ errors,
704
+ missing,
705
+ disabled,
706
+ domain,
707
+ }: InteractionFieldRenderProps<Params, Key> & {
708
+ domain: InputDomain;
709
+ }) {
710
+ const theme = useTheme();
711
+ const selected = new Set(Array.isArray(value) ? (value as string[]) : []);
712
+ const min =
713
+ domain.selection?.mode === "many"
714
+ ? (domain.selection.min ?? 0)
715
+ : domain.min;
716
+ const max =
717
+ (domain.selection?.mode === "many" ? domain.selection.max : domain.max) ??
718
+ domain.choices?.length ??
719
+ Number.POSITIVE_INFINITY;
720
+ const toggle = (choice: string) => {
721
+ const next = new Set(selected);
722
+ if (next.has(choice)) next.delete(choice);
723
+ else if (next.size < max) next.add(choice);
724
+ setValue([...next] as Params[Key]);
725
+ };
726
+ const meta =
727
+ min || Number.isFinite(max)
728
+ ? `Pick ${min ?? 0}${Number.isFinite(max) ? `-${max}` : "+"}`
729
+ : undefined;
730
+ return (
731
+ <FieldFrame
732
+ label={
733
+ <span
734
+ style={{
735
+ display: "flex",
736
+ justifyContent: "space-between",
737
+ gap: theme.space[2],
738
+ }}
739
+ >
740
+ <span>{labelForInput(input)}</span>
741
+ {meta ? (
742
+ <span style={{ color: theme.semantic.text.muted }}>{meta}</span>
743
+ ) : null}
744
+ </span>
745
+ }
746
+ errors={errors}
747
+ missing={missing}
748
+ >
749
+ <span style={{ display: "flex", flexWrap: "wrap", gap: theme.space[1] }}>
750
+ {(domain.choices ?? []).map((choice) => {
751
+ const checked = selected.has(choice.value);
752
+ return (
753
+ <ThemedButton
754
+ key={choice.value}
755
+ type="button"
756
+ variant={checked ? "primary" : "secondary"}
757
+ size="sm"
758
+ disabled={
759
+ disabled ||
760
+ choice.disabled ||
761
+ (!checked && selected.size >= max)
762
+ }
763
+ aria-pressed={checked}
764
+ title={choice.disabledReason ?? choice.description}
765
+ onClick={() => toggle(choice.value)}
766
+ className="h-8 px-3 text-sm"
767
+ >
768
+ <ChoiceOptionLabel choice={choice} />
769
+ </ThemedButton>
770
+ );
771
+ })}
772
+ </span>
773
+ </FieldFrame>
774
+ );
775
+ }
776
+
777
+ function ResourceMapField<
778
+ Params extends InteractionParamsShape,
779
+ Key extends keyof Params & string,
780
+ >({
781
+ input,
782
+ value,
783
+ setValue,
784
+ errors,
785
+ missing,
786
+ disabled,
787
+ domain,
788
+ }: InteractionFieldRenderProps<Params, Key> & {
789
+ domain: InputDomain;
790
+ }) {
791
+ const theme = useTheme();
792
+ const current: Record<string, unknown> = isRecord(value) ? value : {};
793
+ const update = (
794
+ resourceId: string,
795
+ delta: number,
796
+ min: number,
797
+ max: number,
798
+ ) => {
799
+ const previous = numberOrZero(current[resourceId]);
800
+ const next = Math.max(min, Math.min(max, previous + delta));
801
+ setValue({ ...current, [resourceId]: next } as Params[Key]);
802
+ };
803
+ return (
804
+ <FieldFrame label={labelForInput(input)} errors={errors} missing={missing}>
805
+ <span
806
+ style={{
807
+ display: "grid",
808
+ gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))",
809
+ gap: theme.space[2],
810
+ }}
811
+ >
812
+ {(domain.resources ?? []).map((resource) => {
813
+ const min = resource.min ?? 0;
814
+ const max = resource.max ?? 0;
815
+ const amount = numberOrZero(current[resource.resourceId]);
816
+ return (
817
+ <span
818
+ key={resource.resourceId}
819
+ style={{
820
+ display: "grid",
821
+ gridTemplateColumns: "minmax(0, 1fr) auto auto auto",
822
+ alignItems: "center",
823
+ gap: theme.space[1],
824
+ padding: theme.space[2],
825
+ borderRadius: theme.radius.md,
826
+ background: theme.semantic.surface.inset,
827
+ }}
828
+ >
829
+ <span
830
+ style={{
831
+ minWidth: 0,
832
+ display: "inline-flex",
833
+ alignItems: "center",
834
+ gap: theme.space[1],
835
+ }}
836
+ >
837
+ {resource.icon ? (
838
+ <span aria-hidden style={{ fontSize: "1.1em" }}>
839
+ {resource.icon}
840
+ </span>
841
+ ) : null}
842
+ {resource.label ?? humanize(input.key)}
843
+ </span>
844
+ <StepperButton
845
+ label={`Decrease ${resource.label ?? resource.resourceId}`}
846
+ disabled={disabled || amount <= min}
847
+ onClick={() => update(resource.resourceId, -1, min, max)}
848
+ >
849
+ -
850
+ </StepperButton>
851
+ <span
852
+ style={{
853
+ minWidth: "2ch",
854
+ textAlign: "center",
855
+ fontVariantNumeric: "tabular-nums",
856
+ }}
857
+ >
858
+ {amount}
859
+ </span>
860
+ <StepperButton
861
+ label={`Increase ${resource.label ?? resource.resourceId}`}
862
+ disabled={disabled || amount >= max}
863
+ onClick={() => update(resource.resourceId, 1, min, max)}
864
+ >
865
+ +
866
+ </StepperButton>
867
+ </span>
868
+ );
869
+ })}
870
+ </span>
871
+ </FieldFrame>
872
+ );
873
+ }
874
+
875
+ function BoundedNumberField<
876
+ Params extends InteractionParamsShape,
877
+ Key extends keyof Params & string,
878
+ >({
879
+ input,
880
+ value,
881
+ setValue,
882
+ errors,
883
+ missing,
884
+ disabled,
885
+ domain,
886
+ }: InteractionFieldRenderProps<Params, Key> & {
887
+ domain: InputDomain;
888
+ }) {
889
+ const theme = useTheme();
890
+ const min = domain.min ?? 0;
891
+ const max = domain.max ?? Number.POSITIVE_INFINITY;
892
+ const step = domain.step ?? 1;
893
+ const current = typeof value === "number" ? value : min;
894
+ const controlId = useId();
895
+ const update = (next: number) =>
896
+ setValue(Math.max(min, Math.min(max, next)) as Params[Key]);
897
+ return (
898
+ <FieldFrame
899
+ label={labelForInput(input)}
900
+ controlId={controlId}
901
+ errors={errors}
902
+ missing={missing}
903
+ >
904
+ <span
905
+ style={{
906
+ display: "inline-flex",
907
+ alignItems: "center",
908
+ gap: theme.space[1],
909
+ }}
910
+ >
911
+ <StepperButton
912
+ label={`Decrease ${input.key}`}
913
+ disabled={disabled || current <= min}
914
+ onClick={() => update(current - step)}
915
+ >
916
+ -
917
+ </StepperButton>
918
+ <Input
919
+ id={controlId}
920
+ type="number"
921
+ min={min}
922
+ max={Number.isFinite(max) ? max : undefined}
923
+ step={step}
924
+ value={current}
925
+ disabled={disabled}
926
+ onChange={(event) => update(Number(event.target.value))}
927
+ className="h-9 w-[8ch] px-2 text-center text-sm md:text-sm"
928
+ />
929
+ <StepperButton
930
+ label={`Increase ${input.key}`}
931
+ disabled={disabled || current >= max}
932
+ onClick={() => update(current + step)}
933
+ >
934
+ +
935
+ </StepperButton>
936
+ </span>
937
+ </FieldFrame>
938
+ );
939
+ }
940
+
941
+ function TargetSummaryField<
942
+ Params extends InteractionParamsShape,
943
+ Key extends keyof Params & string,
944
+ >({
945
+ input,
946
+ value,
947
+ errors,
948
+ missing,
949
+ domain,
950
+ }: InteractionFieldRenderProps<Params, Key> & {
951
+ domain: InputDomain;
952
+ }) {
953
+ return (
954
+ <FieldFrame label={labelForInput(input)} errors={errors} missing={missing}>
955
+ <span>
956
+ {Array.isArray(value)
957
+ ? value.length > 0
958
+ ? `${value.length} selected: ${value.join(", ")}`
959
+ : `Select ${targetSelectionLabel(domain)}.`
960
+ : typeof value === "string"
961
+ ? value
962
+ : `Select a ${domain.targetKind ?? "target"} on the board.`}
963
+ </span>
964
+ </FieldFrame>
965
+ );
966
+ }
967
+
968
+ function targetSelectionLabel(domain: InputDomain): string {
969
+ const target = domain.targetKind ?? "target";
970
+ const selection = domain.selection;
971
+ if (selection?.mode !== "many") return `a ${target}`;
972
+ const min = selection.min ?? 0;
973
+ if (selection.max !== undefined && min === selection.max) {
974
+ return `${min} ${target}${min === 1 ? "" : "s"}`;
975
+ }
976
+ return `${min}${selection.max ? `-${selection.max}` : "+"} ${target}s`;
977
+ }
978
+
979
+ function StepperButton({
980
+ label,
981
+ disabled,
982
+ onClick,
983
+ children,
984
+ }: {
985
+ label: string;
986
+ disabled: boolean;
987
+ onClick: () => void;
988
+ children: ReactNode;
989
+ }) {
990
+ return (
991
+ <ThemedButton
992
+ type="button"
993
+ variant="secondary"
994
+ size="sm"
995
+ aria-label={label}
996
+ disabled={disabled}
997
+ onClick={onClick}
998
+ className="h-8 w-8 text-sm"
999
+ >
1000
+ {children}
1001
+ </ThemedButton>
1002
+ );
1003
+ }
1004
+
1005
+ function labelForInput(input: InteractionInputDescriptor): string {
1006
+ if (input.domain.type === "choice") {
1007
+ const exact = input.domain.choices?.find(
1008
+ (choice) => choice.value === input.defaultValue,
1009
+ );
1010
+ if (exact?.label && input.key === exact.value) return exact.label;
1011
+ }
1012
+ return humanize(input.key);
1013
+ }
1014
+
1015
+ function humanize(key: string): string {
1016
+ return key
1017
+ .replace(/Id$/, "")
1018
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
1019
+ .replace(/[-_]+/g, " ")
1020
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
1021
+ }
1022
+
1023
+ function isRecord(value: unknown): value is Record<string, unknown> {
1024
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1025
+ }
1026
+
1027
+ function numberOrZero(value: unknown): number {
1028
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1029
+ }