@dreamboard-games/sdk 0.2.0

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 (369) hide show
  1. package/LICENSE.md +96 -0
  2. package/README.md +12 -0
  3. package/dist/HandView-ncJIVLhN.d.ts +193 -0
  4. package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
  5. package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
  6. package/dist/bundle-TIZcw8LB.d.ts +281 -0
  7. package/dist/cards-Sl3b40Mv.d.ts +13 -0
  8. package/dist/chunk-7YAHLYBR.js +481 -0
  9. package/dist/chunk-7YAHLYBR.js.map +1 -0
  10. package/dist/chunk-FDNZTDD6.js +8085 -0
  11. package/dist/chunk-FDNZTDD6.js.map +1 -0
  12. package/dist/chunk-GKKBPPSW.js +598 -0
  13. package/dist/chunk-GKKBPPSW.js.map +1 -0
  14. package/dist/chunk-I46YJSOD.js +1 -0
  15. package/dist/chunk-I46YJSOD.js.map +1 -0
  16. package/dist/chunk-KAELH4KC.js +104 -0
  17. package/dist/chunk-KAELH4KC.js.map +1 -0
  18. package/dist/chunk-PZ5AY32C.js +10 -0
  19. package/dist/chunk-PZ5AY32C.js.map +1 -0
  20. package/dist/chunk-T3ZKNUZ7.js +1 -0
  21. package/dist/chunk-T3ZKNUZ7.js.map +1 -0
  22. package/dist/chunk-T52J5RMF.js +1 -0
  23. package/dist/chunk-T52J5RMF.js.map +1 -0
  24. package/dist/chunk-TDSWKVZ4.js +5401 -0
  25. package/dist/chunk-TDSWKVZ4.js.map +1 -0
  26. package/dist/chunk-U5C6BONG.js +34 -0
  27. package/dist/chunk-U5C6BONG.js.map +1 -0
  28. package/dist/chunk-VDXOF4FW.js +69 -0
  29. package/dist/chunk-VDXOF4FW.js.map +1 -0
  30. package/dist/chunk-VFTAA4WO.js +115 -0
  31. package/dist/chunk-VFTAA4WO.js.map +1 -0
  32. package/dist/chunk-WN74KVNY.js +17 -0
  33. package/dist/chunk-WN74KVNY.js.map +1 -0
  34. package/dist/chunk-WYPQ3GG5.js +10990 -0
  35. package/dist/chunk-WYPQ3GG5.js.map +1 -0
  36. package/dist/components-D5ZRE2Hl.d.ts +1451 -0
  37. package/dist/generated/runtime/primitives.d.ts +12 -0
  38. package/dist/generated/runtime/primitives.js +180 -0
  39. package/dist/generated/runtime/primitives.js.map +1 -0
  40. package/dist/generated/runtime-api.d.ts +3 -0
  41. package/dist/generated/runtime-api.js +2 -0
  42. package/dist/generated/runtime-api.js.map +1 -0
  43. package/dist/generated/runtime.d.ts +14 -0
  44. package/dist/generated/runtime.js +18 -0
  45. package/dist/generated/runtime.js.map +1 -0
  46. package/dist/generated/workspace-contract.d.ts +14 -0
  47. package/dist/generated/workspace-contract.js +14 -0
  48. package/dist/generated/workspace-contract.js.map +1 -0
  49. package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
  50. package/dist/hex-color-MhOyuY-o.d.ts +8 -0
  51. package/dist/index-BwqPQtBu.d.ts +1433 -0
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.js +12 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
  56. package/dist/infrastructure/reducer-bundle-abi.js +14 -0
  57. package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
  58. package/dist/infrastructure/workspace-codegen.d.ts +53 -0
  59. package/dist/infrastructure/workspace-codegen.js +44 -0
  60. package/dist/infrastructure/workspace-codegen.js.map +1 -0
  61. package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
  62. package/dist/package-set.d.ts +13 -0
  63. package/dist/package-set.js +12 -0
  64. package/dist/package-set.js.map +1 -0
  65. package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
  66. package/dist/reducer.d.ts +3786 -0
  67. package/dist/reducer.js +8131 -0
  68. package/dist/reducer.js.map +1 -0
  69. package/dist/runtime/primitives.d.ts +226 -0
  70. package/dist/runtime/primitives.js +180 -0
  71. package/dist/runtime/primitives.js.map +1 -0
  72. package/dist/runtime/types/runtime-api.d.ts +1 -0
  73. package/dist/runtime/types/runtime-api.js +2 -0
  74. package/dist/runtime/types/runtime-api.js.map +1 -0
  75. package/dist/runtime/workspace-contract.d.ts +172 -0
  76. package/dist/runtime/workspace-contract.js +14 -0
  77. package/dist/runtime/workspace-contract.js.map +1 -0
  78. package/dist/runtime-api-3dshj6kK.d.ts +101 -0
  79. package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
  80. package/dist/runtime.d.ts +58 -0
  81. package/dist/runtime.js +13 -0
  82. package/dist/runtime.js.map +1 -0
  83. package/dist/slots-1GPGihk8.d.ts +8 -0
  84. package/dist/testing.d.ts +149 -0
  85. package/dist/testing.js +513 -0
  86. package/dist/testing.js.map +1 -0
  87. package/dist/types.d.ts +496 -0
  88. package/dist/types.js +28 -0
  89. package/dist/types.js.map +1 -0
  90. package/dist/ui/components.d.ts +16 -0
  91. package/dist/ui/components.js +192 -0
  92. package/dist/ui/components.js.map +1 -0
  93. package/dist/ui/defaults.d.ts +19 -0
  94. package/dist/ui/defaults.js +104 -0
  95. package/dist/ui/defaults.js.map +1 -0
  96. package/dist/ui/plugin-styles.css +250 -0
  97. package/dist/ui/types/player-state.d.ts +365 -0
  98. package/dist/ui/types/player-state.js +1 -0
  99. package/dist/ui/types/player-state.js.map +1 -0
  100. package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
  101. package/dist/ui.d.ts +320 -0
  102. package/dist/ui.js +253 -0
  103. package/dist/ui.js.map +1 -0
  104. package/package.json +199 -0
  105. package/src/generated/reducer-contract/builders.ts +90 -0
  106. package/src/generated/reducer-contract/version.ts +9 -0
  107. package/src/generated/reducer-contract/wire.ts +100 -0
  108. package/src/generated/reducer-contract/zod.ts +101 -0
  109. package/src/generated/runtime/primitives.ts +2 -0
  110. package/src/generated/runtime-api.ts +5 -0
  111. package/src/generated/runtime.ts +35 -0
  112. package/src/generated/workspace-contract.ts +2 -0
  113. package/src/index.ts +7 -0
  114. package/src/infrastructure/reducer-bundle-abi.ts +8 -0
  115. package/src/infrastructure/reducer-contract/bundle.ts +37 -0
  116. package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
  117. package/src/infrastructure/workspace-codegen/index.ts +64 -0
  118. package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
  119. package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
  120. package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
  121. package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
  122. package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
  123. package/src/infrastructure/workspace-codegen.ts +1 -0
  124. package/src/package-set.ts +19 -0
  125. package/src/reducer/authoring/contract.ts +157 -0
  126. package/src/reducer/authoring/effect.ts +224 -0
  127. package/src/reducer/authoring/game.ts +23 -0
  128. package/src/reducer/authoring/interaction.ts +98 -0
  129. package/src/reducer/authoring/phase.ts +300 -0
  130. package/src/reducer/authoring/types.ts +70 -0
  131. package/src/reducer/authoring/validation.ts +382 -0
  132. package/src/reducer/authoring/view-stage.ts +68 -0
  133. package/src/reducer/authoring.ts +29 -0
  134. package/src/reducer/bundle/ingress-bundle.ts +491 -0
  135. package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
  136. package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
  137. package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
  138. package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
  139. package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
  140. package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
  141. package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
  142. package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
  143. package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
  144. package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
  145. package/src/reducer/bundle/trusted/projection-context.ts +39 -0
  146. package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
  147. package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
  148. package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
  149. package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
  150. package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
  151. package/src/reducer/bundle/trusted/static-projection.ts +116 -0
  152. package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
  153. package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
  154. package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
  155. package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
  156. package/src/reducer/bundle/trusted-bundle.ts +97 -0
  157. package/src/reducer/bundle/types.ts +171 -0
  158. package/src/reducer/bundle.ts +2 -0
  159. package/src/reducer/client-param-schemas.ts +57 -0
  160. package/src/reducer/compose.ts +34 -0
  161. package/src/reducer/core/runtime-input.ts +30 -0
  162. package/src/reducer/core/runtime-instruction.ts +59 -0
  163. package/src/reducer/core/types.ts +62 -0
  164. package/src/reducer/definition-index.ts +277 -0
  165. package/src/reducer/derived.ts +106 -0
  166. package/src/reducer/effects.ts +92 -0
  167. package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
  168. package/src/reducer/ingress/decode-runtime-input.ts +7 -0
  169. package/src/reducer/ingress/decode-session-state.ts +9 -0
  170. package/src/reducer/ingress/encode-session-state.ts +6 -0
  171. package/src/reducer/ingress/input-codec.ts +18 -0
  172. package/src/reducer/ingress/phase-schemas.ts +62 -0
  173. package/src/reducer/ingress/raw-types.ts +107 -0
  174. package/src/reducer/ingress/runtime-codec.ts +14 -0
  175. package/src/reducer/ingress/runtime-payload.ts +13 -0
  176. package/src/reducer/ingress/session-codec.ts +392 -0
  177. package/src/reducer/ingress/types.ts +6 -0
  178. package/src/reducer/inputs/boardInput.ts +217 -0
  179. package/src/reducer/inputs/boardTarget.ts +190 -0
  180. package/src/reducer/inputs/cardInput.ts +86 -0
  181. package/src/reducer/inputs/cardTarget.ts +101 -0
  182. package/src/reducer/inputs/choiceTarget.ts +104 -0
  183. package/src/reducer/inputs/defineInputs.ts +71 -0
  184. package/src/reducer/inputs/formInput.ts +809 -0
  185. package/src/reducer/inputs/many.ts +120 -0
  186. package/src/reducer/inputs/promptInput.ts +87 -0
  187. package/src/reducer/inputs/rngInput.ts +58 -0
  188. package/src/reducer/inputs/targetRule.ts +123 -0
  189. package/src/reducer/inputs.ts +41 -0
  190. package/src/reducer/model/definition.ts +1072 -0
  191. package/src/reducer/model/extract.ts +745 -0
  192. package/src/reducer/model/manifest.ts +570 -0
  193. package/src/reducer/model/queries.ts +641 -0
  194. package/src/reducer/model/runtime.ts +264 -0
  195. package/src/reducer/model/spec.ts +1386 -0
  196. package/src/reducer/model/table.ts +260 -0
  197. package/src/reducer/model.ts +7 -0
  198. package/src/reducer/ops.ts +1034 -0
  199. package/src/reducer/parse-utils.ts +28 -0
  200. package/src/reducer/per-player.ts +422 -0
  201. package/src/reducer/rng.ts +69 -0
  202. package/src/reducer/schema-helpers.ts +185 -0
  203. package/src/reducer/setup-bootstrap-helpers.ts +171 -0
  204. package/src/reducer/setup-bootstrap.ts +481 -0
  205. package/src/reducer/table-ops.ts +2671 -0
  206. package/src/reducer/table-queries.ts +372 -0
  207. package/src/reducer/transaction.ts +120 -0
  208. package/src/reducer.ts +314 -0
  209. package/src/runtime/primitives.ts +1 -0
  210. package/src/runtime/types/runtime-api.ts +1 -0
  211. package/src/runtime/workspace-contract.ts +32 -0
  212. package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
  213. package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
  214. package/src/runtime-internal/components/board/target-layer.ts +70 -0
  215. package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
  216. package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
  217. package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
  218. package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
  219. package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
  220. package/src/runtime-internal/defaults/components.tsx +409 -0
  221. package/src/runtime-internal/defaults/index.ts +11 -0
  222. package/src/runtime-internal/errors/ValidationError.ts +29 -0
  223. package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
  224. package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
  225. package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
  226. package/src/runtime-internal/hooks/useGameView.ts +9 -0
  227. package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
  228. package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
  229. package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
  230. package/src/runtime-internal/hooks/useLobby.ts +76 -0
  231. package/src/runtime-internal/hooks/useMe.ts +48 -0
  232. package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
  233. package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
  234. package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
  235. package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
  236. package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
  237. package/src/runtime-internal/index.ts +42 -0
  238. package/src/runtime-internal/internal.ts +43 -0
  239. package/src/runtime-internal/plugin-styles.css +250 -0
  240. package/src/runtime-internal/primitives/board.tsx +459 -0
  241. package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
  242. package/src/runtime-internal/primitives/dice.tsx +79 -0
  243. package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
  244. package/src/runtime-internal/primitives/game.tsx +387 -0
  245. package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
  246. package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
  247. package/src/runtime-internal/primitives/index.ts +196 -0
  248. package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
  249. package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
  250. package/src/runtime-internal/primitives/interaction.tsx +987 -0
  251. package/src/runtime-internal/primitives/phase.tsx +43 -0
  252. package/src/runtime-internal/primitives/player-roster.tsx +302 -0
  253. package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
  254. package/src/runtime-internal/primitives/prompt.tsx +255 -0
  255. package/src/runtime-internal/primitives/ui.tsx +60 -0
  256. package/src/runtime-internal/primitives/zone.tsx +791 -0
  257. package/src/runtime-internal/reducer.ts +30 -0
  258. package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
  259. package/src/runtime-internal/types/plugin-state.ts +508 -0
  260. package/src/runtime-internal/types/reducer-state.ts +24 -0
  261. package/src/runtime-internal/types/runtime-api.ts +114 -0
  262. package/src/runtime-internal/ui-contract.ts +519 -0
  263. package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
  264. package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
  265. package/src/runtime-internal/utils/interaction-labels.ts +23 -0
  266. package/src/runtime-internal/utils/interaction-router.ts +273 -0
  267. package/src/runtime-internal/utils/interaction-status.ts +74 -0
  268. package/src/runtime-internal/workspace-contract.ts +1170 -0
  269. package/src/runtime.ts +34 -0
  270. package/src/testing/create-expect-api.ts +352 -0
  271. package/src/testing/create-test-runtime.ts +381 -0
  272. package/src/testing/definitions.ts +127 -0
  273. package/src/testing/index.ts +3 -0
  274. package/src/testing.ts +1 -0
  275. package/src/type-stubs/manifest-contract.d.ts +42 -0
  276. package/src/type-stubs/manifest-contract.js +72 -0
  277. package/src/type-stubs/ui-contract.d.ts +5 -0
  278. package/src/type-stubs/ui-contract.js +1 -0
  279. package/src/types/authoring-card-properties.type-test.ts +266 -0
  280. package/src/types/authoring.ts +1282 -0
  281. package/src/types/cards.ts +19 -0
  282. package/src/types/contracts.ts +1550 -0
  283. package/src/types/generated-helpers.ts +35 -0
  284. package/src/types/index.ts +147 -0
  285. package/src/types/slots.ts +11 -0
  286. package/src/types.ts +1 -0
  287. package/src/ui/components/ActionButton.tsx +97 -0
  288. package/src/ui/components/ActionPanel.tsx +315 -0
  289. package/src/ui/components/Card.tsx +378 -0
  290. package/src/ui/components/CardDragSurface.tsx +1076 -0
  291. package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
  292. package/src/ui/components/CostDisplay.tsx +145 -0
  293. package/src/ui/components/DiceRoller.tsx +581 -0
  294. package/src/ui/components/Drawer.tsx +180 -0
  295. package/src/ui/components/ErrorBoundary.tsx +275 -0
  296. package/src/ui/components/GameEndDisplay.tsx +398 -0
  297. package/src/ui/components/GameSkeleton.tsx +260 -0
  298. package/src/ui/components/Hand.tsx +468 -0
  299. package/src/ui/components/HandDock.tsx +299 -0
  300. package/src/ui/components/HandView.tsx +441 -0
  301. package/src/ui/components/MobileHandTray.tsx +381 -0
  302. package/src/ui/components/MoreActions.tsx +143 -0
  303. package/src/ui/components/PhaseIndicator.tsx +341 -0
  304. package/src/ui/components/PlayArea.tsx +146 -0
  305. package/src/ui/components/PrimaryActionButton.tsx +336 -0
  306. package/src/ui/components/PrimaryButton.tsx +45 -0
  307. package/src/ui/components/ResourceCounter.tsx +270 -0
  308. package/src/ui/components/StagingZone.tsx +134 -0
  309. package/src/ui/components/ThemedButton.tsx +113 -0
  310. package/src/ui/components/Toast.tsx +264 -0
  311. package/src/ui/components/board/HexGrid.tsx +1294 -0
  312. package/src/ui/components/board/NetworkGraph.tsx +476 -0
  313. package/src/ui/components/board/SlotSystem.tsx +388 -0
  314. package/src/ui/components/board/SquareGrid.tsx +1165 -0
  315. package/src/ui/components/board/TrackBoard.tsx +496 -0
  316. package/src/ui/components/board/ZoneMap.tsx +448 -0
  317. package/src/ui/components/board/hex-board-view.ts +123 -0
  318. package/src/ui/components/board/index.ts +142 -0
  319. package/src/ui/components/board/interaction-accessibility.ts +21 -0
  320. package/src/ui/components/board/target-layer.ts +66 -0
  321. package/src/ui/components/card-render-content.type-test.ts +27 -0
  322. package/src/ui/components/hand-layout-math.ts +163 -0
  323. package/src/ui/components/hand-pointer-engine.ts +413 -0
  324. package/src/ui/components/index.ts +245 -0
  325. package/src/ui/components.ts +1 -0
  326. package/src/ui/defaults/components.tsx +106 -0
  327. package/src/ui/defaults/index.ts +8 -0
  328. package/src/ui/defaults.ts +1 -0
  329. package/src/ui/errors/ValidationError.ts +29 -0
  330. package/src/ui/helpers/cards.ts +19 -0
  331. package/src/ui/helpers/track-board.ts +211 -0
  332. package/src/ui/hooks/useBoardTopology.ts +316 -0
  333. package/src/ui/hooks/useCards.ts +10 -0
  334. package/src/ui/hooks/useHandCardPointer.ts +381 -0
  335. package/src/ui/hooks/useHandLayout.ts +378 -0
  336. package/src/ui/hooks/useHandPresentation.ts +121 -0
  337. package/src/ui/hooks/useHexBoard.ts +74 -0
  338. package/src/ui/hooks/useHexGrid.ts +185 -0
  339. package/src/ui/hooks/useIsMobile.ts +35 -0
  340. package/src/ui/hooks/usePanZoom.ts +278 -0
  341. package/src/ui/hooks/useSquareBoard.ts +124 -0
  342. package/src/ui/hooks/useSquareGrid.ts +328 -0
  343. package/src/ui/index.ts +98 -0
  344. package/src/ui/internal/ui/alert.tsx +51 -0
  345. package/src/ui/internal/ui/button.tsx +58 -0
  346. package/src/ui/internal/ui/dialog.tsx +134 -0
  347. package/src/ui/internal/ui/input.tsx +21 -0
  348. package/src/ui/internal/ui/label.tsx +21 -0
  349. package/src/ui/internal/ui/select.tsx +129 -0
  350. package/src/ui/internal/ui/tooltip.tsx +54 -0
  351. package/src/ui/internal/ui/utils.ts +5 -0
  352. package/src/ui/plugin-styles.css +250 -0
  353. package/src/ui/primitives/dialog-lifecycle.ts +58 -0
  354. package/src/ui/primitives/dice.tsx +79 -0
  355. package/src/ui/primitives/primitive-props.tsx +101 -0
  356. package/src/ui/theme/ThemeProvider.tsx +252 -0
  357. package/src/ui/theme/board.ts +61 -0
  358. package/src/ui/theme/css-vars.ts +105 -0
  359. package/src/ui/theme/derive.ts +240 -0
  360. package/src/ui/theme/index.ts +61 -0
  361. package/src/ui/theme/presets/arcade.ts +261 -0
  362. package/src/ui/theme/presets/studio.ts +261 -0
  363. package/src/ui/theme/presets/tabletop.ts +266 -0
  364. package/src/ui/theme/tokens.ts +392 -0
  365. package/src/ui/types/hex-color.ts +20 -0
  366. package/src/ui/types/player-state.ts +463 -0
  367. package/src/ui/types/tiled-board.ts +785 -0
  368. package/src/ui/types/visual-state.ts +137 -0
  369. package/src/ui.ts +1 -0
@@ -0,0 +1,987 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ type ButtonHTMLAttributes,
9
+ type HTMLAttributes,
10
+ type InputHTMLAttributes,
11
+ type ReactNode,
12
+ } from "react";
13
+ import {
14
+ useInteractionHandle,
15
+ type InteractionHandle,
16
+ type InteractionParamsShape,
17
+ } from "../hooks/useInteractionHandle.js";
18
+ import {
19
+ useInteractionUiStore,
20
+ usePendingInteractionKey,
21
+ usePendingInteractionRevision,
22
+ } from "../context/InteractionDraftContext.js";
23
+ import { usePluginState } from "../context/PluginStateContext.js";
24
+ import type { InteractionInputKey, InteractionKey } from "../ui-contract.js";
25
+ import type {
26
+ InteractionDescriptor,
27
+ InteractionInputDescriptor,
28
+ ZoneHandlesSnapshot,
29
+ } from "../types/plugin-state.js";
30
+ import {
31
+ hasInteractionFieldErrors,
32
+ inputByKey,
33
+ isBoardTargetDomain,
34
+ isResolvedTargetDomain,
35
+ isTargetDomain,
36
+ isManyTargetSelectable,
37
+ } from "../utils/interaction-inputs.js";
38
+ import {
39
+ getInteractionDraftReadiness,
40
+ markInteractionPending,
41
+ routeCardInputIntent,
42
+ shouldRouteInteractionPending,
43
+ } from "../utils/interaction-router.js";
44
+ import {
45
+ interactionUnavailableReason,
46
+ isInteractionAvailable,
47
+ } from "../utils/interaction-status.js";
48
+ import { interactionLabel } from "../utils/interaction-labels.js";
49
+ import {
50
+ composeEventHandlers,
51
+ renderPrimitive,
52
+ type PrimitiveCommonProps,
53
+ } from "./primitive-props.js";
54
+ import {
55
+ BoundInteractionForm,
56
+ castInteractionDraft,
57
+ castInteractionHandle,
58
+ type BoundInteractionFormProps,
59
+ } from "./interaction-form-binding.js";
60
+ import {
61
+ InteractionField as BaseInteractionField,
62
+ type InteractionFieldProps as BaseInteractionFieldProps,
63
+ } from "../components/InteractionForm.js";
64
+ import {
65
+ submitInteractionDraft,
66
+ submitInteractionParams,
67
+ type InteractionSubmitCallbacks,
68
+ } from "./interaction-submit.js";
69
+ import {
70
+ useDialogLifecycle,
71
+ type DialogLifecycleState,
72
+ } from "./dialog-lifecycle.js";
73
+ import { useGameActionError } from "./game.js";
74
+ import { useOptionalZonePrimitiveContext, useZoneCardContext } from "./zone.js";
75
+
76
+ interface InteractionContextValue {
77
+ interaction: string;
78
+ descriptor: InteractionDescriptor | null;
79
+ handle: ReturnType<typeof useInteractionHandle> | null;
80
+ }
81
+
82
+ const InteractionContext = createContext<InteractionContextValue | null>(null);
83
+
84
+ function humanizeInteraction(value: string): string {
85
+ const parts = value.split(".");
86
+ const leaf = parts[parts.length - 1] ?? value;
87
+ return leaf
88
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
89
+ .replace(/[-_]+/g, " ")
90
+ .replace(/\s+/g, " ")
91
+ .trim()
92
+ .replace(/^./, (first: string) => first.toUpperCase());
93
+ }
94
+
95
+ export function useInteractionPrimitiveContext(): InteractionContextValue {
96
+ const value = useContext(InteractionContext);
97
+ if (!value) {
98
+ throw new Error(
99
+ "Interaction primitives must be rendered inside <Interaction.Root>.",
100
+ );
101
+ }
102
+ return value;
103
+ }
104
+
105
+ /**
106
+ * Live draft value for the active interaction's card-target input, resolved
107
+ * from the surrounding `<Interaction.Root>`. Returns the selected card-id array
108
+ * for `selection: "many"` inputs and the single id for `selection: "one"`.
109
+ * Returns `undefined` when there is no interaction context or no card-target
110
+ * input (so it is safe to render outside a root). Reactive: `handle.values`
111
+ * updates as the draft changes. Backs the card surface `slot.card.Value`.
112
+ */
113
+ export function useResolvedCardTargetValue(): unknown {
114
+ const context = useContext(InteractionContext);
115
+ const descriptor = context?.descriptor;
116
+ const handle = context?.handle;
117
+ if (!descriptor || !handle) return undefined;
118
+ const cardInput = descriptor.inputs.find(
119
+ (input) =>
120
+ isTargetDomain(input.domain) && input.domain.type === "cardTarget",
121
+ );
122
+ if (!cardInput) return undefined;
123
+ return (handle.values as Record<string, unknown>)[cardInput.key];
124
+ }
125
+
126
+ function useInteractionDescriptor(interaction: string) {
127
+ return usePluginState((state) =>
128
+ state.gameplay.availableInteractions.find(
129
+ (descriptor) =>
130
+ descriptor.interactionKey === interaction ||
131
+ descriptor.interactionId === interaction,
132
+ ),
133
+ );
134
+ }
135
+
136
+ export interface InteractionRootProps<
137
+ Interaction extends string = InteractionKey,
138
+ > {
139
+ interaction: Interaction;
140
+ children: ReactNode;
141
+ unavailable?: "render" | "hide";
142
+ }
143
+
144
+ function ResolvedInteractionRoot({
145
+ interaction,
146
+ descriptor,
147
+ children,
148
+ }: {
149
+ interaction: string;
150
+ descriptor: InteractionDescriptor;
151
+ children: ReactNode;
152
+ }) {
153
+ const handle = useInteractionHandle(descriptor);
154
+ const value = useMemo<InteractionContextValue>(
155
+ () => ({ interaction, descriptor, handle }),
156
+ [descriptor, handle, interaction],
157
+ );
158
+ return (
159
+ <InteractionContext.Provider value={value}>
160
+ {children}
161
+ </InteractionContext.Provider>
162
+ );
163
+ }
164
+
165
+ export function InteractionRoot<Interaction extends string = InteractionKey>({
166
+ interaction,
167
+ children,
168
+ unavailable = "render",
169
+ }: InteractionRootProps<Interaction>) {
170
+ const descriptor = useInteractionDescriptor(interaction);
171
+ if (!descriptor) {
172
+ if (unavailable === "hide") return null;
173
+ return (
174
+ <InteractionContext.Provider
175
+ value={{
176
+ interaction,
177
+ descriptor: null,
178
+ handle: null,
179
+ }}
180
+ >
181
+ {children}
182
+ </InteractionContext.Provider>
183
+ );
184
+ }
185
+ if (!isInteractionAvailable(descriptor) && unavailable === "hide") {
186
+ return null;
187
+ }
188
+ return (
189
+ <ResolvedInteractionRoot interaction={interaction} descriptor={descriptor}>
190
+ {children}
191
+ </ResolvedInteractionRoot>
192
+ );
193
+ }
194
+
195
+ export type InteractionDialogState = DialogLifecycleState;
196
+
197
+ export interface InteractionDialogRenderState<
198
+ Interaction extends string = InteractionKey,
199
+ > {
200
+ interaction: Interaction;
201
+ state: InteractionDialogState;
202
+ open: boolean;
203
+ minimized: boolean;
204
+ dismissed: boolean;
205
+ setOpen: (open: boolean) => void;
206
+ restore: () => void;
207
+ minimize: () => void;
208
+ dismiss: () => void;
209
+ }
210
+
211
+ export interface InteractionDialogProps<
212
+ Interaction extends string = InteractionKey,
213
+ > {
214
+ defaultOpen?: boolean;
215
+ onStateChange?: (state: InteractionDialogState) => void;
216
+ children: (state: InteractionDialogRenderState<Interaction>) => ReactNode;
217
+ }
218
+
219
+ export function InteractionDialog<Interaction extends string = InteractionKey>({
220
+ defaultOpen = false,
221
+ onStateChange,
222
+ children,
223
+ }: InteractionDialogProps<Interaction>) {
224
+ const { interaction } = useInteractionPrimitiveContext();
225
+ const pendingInteractionKey = usePendingInteractionKey();
226
+ const pendingInteractionRevision = usePendingInteractionRevision();
227
+ const lifecycle = useDialogLifecycle({ defaultOpen, onStateChange });
228
+ const restoredRevisionRef = useRef<number | null>(null);
229
+ useEffect(() => {
230
+ if (
231
+ pendingInteractionKey === interaction &&
232
+ restoredRevisionRef.current !== pendingInteractionRevision
233
+ ) {
234
+ restoredRevisionRef.current = pendingInteractionRevision;
235
+ lifecycle.restore();
236
+ }
237
+ }, [
238
+ interaction,
239
+ lifecycle,
240
+ pendingInteractionKey,
241
+ pendingInteractionRevision,
242
+ ]);
243
+ const renderState = useMemo<InteractionDialogRenderState<Interaction>>(
244
+ () => ({
245
+ interaction: interaction as Interaction,
246
+ ...lifecycle,
247
+ }),
248
+ [interaction, lifecycle],
249
+ );
250
+ return <>{children(renderState)}</>;
251
+ }
252
+
253
+ export interface InteractionSwitchRenderState<
254
+ Interaction extends string = InteractionKey,
255
+ > {
256
+ interaction: Interaction;
257
+ descriptor: InteractionDescriptor<Interaction>;
258
+ }
259
+
260
+ export type InteractionSwitchRouteMap<
261
+ Interaction extends string = InteractionKey,
262
+ > = {
263
+ [Key in Interaction]?: (
264
+ state: InteractionSwitchRenderState<Key>,
265
+ ) => ReactNode;
266
+ };
267
+
268
+ export interface InteractionRoute {
269
+ readonly collect: Record<string, unknown>;
270
+ }
271
+
272
+ export type InteractionRoutesMap<Interaction extends string = InteractionKey> =
273
+ {
274
+ [Key in Interaction]: InteractionRoute;
275
+ };
276
+
277
+ export interface InteractionSwitchProps<
278
+ Interaction extends string = InteractionKey,
279
+ > {
280
+ interaction?: Interaction;
281
+ routes: InteractionSwitchRouteMap<Interaction>;
282
+ fallback?: ReactNode;
283
+ }
284
+
285
+ export function InteractionSwitch<Interaction extends string = InteractionKey>({
286
+ interaction,
287
+ routes,
288
+ fallback = null,
289
+ }: InteractionSwitchProps<Interaction>) {
290
+ const pendingInteractionKey = usePendingInteractionKey();
291
+ const descriptors = usePluginState(
292
+ (state) => state.gameplay.availableInteractions,
293
+ );
294
+ const routedInteraction = interaction ?? pendingInteractionKey;
295
+ const descriptor = routedInteraction
296
+ ? descriptors.find(
297
+ (candidate) => candidate.interactionKey === routedInteraction,
298
+ )
299
+ : undefined;
300
+ if (!descriptor) return <>{fallback}</>;
301
+ const route =
302
+ routes[descriptor.interactionKey as keyof typeof routes] ?? null;
303
+ if (!route) return <>{fallback}</>;
304
+ const typedInteraction = descriptor.interactionKey as Interaction;
305
+ return (
306
+ <InteractionRoot interaction={typedInteraction}>
307
+ {route({
308
+ interaction: typedInteraction,
309
+ descriptor: descriptor as InteractionDescriptor<Interaction>,
310
+ })}
311
+ </InteractionRoot>
312
+ );
313
+ }
314
+
315
+ export interface InteractionRoutesProps<
316
+ Interaction extends string = InteractionKey,
317
+ > {
318
+ routes: InteractionRoutesMap<Interaction>;
319
+ fallback?: ReactNode;
320
+ includeUnavailable?: boolean | null;
321
+ }
322
+
323
+ const warnedInteractionRouteIssues = new Set<string>();
324
+
325
+ function warnInteractionRouteIssue(message: string) {
326
+ if (warnedInteractionRouteIssues.has(message)) return;
327
+ warnedInteractionRouteIssues.add(message);
328
+ console.warn(message);
329
+ }
330
+
331
+ export function InteractionRoutes<Interaction extends string = InteractionKey>({
332
+ routes,
333
+ fallback = null,
334
+ includeUnavailable = false,
335
+ }: InteractionRoutesProps<Interaction>) {
336
+ const descriptors = usePluginState(
337
+ (state) => state.gameplay.availableInteractions,
338
+ );
339
+ if (descriptors.length === 0) return <>{fallback}</>;
340
+ const routedDescriptors = descriptors
341
+ .filter(
342
+ (descriptor) => includeUnavailable || isInteractionAvailable(descriptor),
343
+ )
344
+ .map((descriptor) => {
345
+ const interaction = descriptor.interactionKey as Interaction;
346
+ const route = routes[interaction as keyof typeof routes];
347
+ if (!route) {
348
+ warnInteractionRouteIssue(
349
+ `[dreamboard] Interaction.Routes is missing a collector route for "${descriptor.interactionKey}". Declare the interaction in routes so input collection stays explicit.`,
350
+ );
351
+ return null;
352
+ }
353
+ const missingInputs = descriptor.inputs
354
+ .map((input) => input.key)
355
+ .filter((input) => !(input in route.collect));
356
+ if (missingInputs.length > 0) {
357
+ warnInteractionRouteIssue(
358
+ `[dreamboard] Interaction.Routes route "${descriptor.interactionKey}" is missing collectors for: ${missingInputs.join(
359
+ ", ",
360
+ )}.`,
361
+ );
362
+ }
363
+ return descriptor;
364
+ });
365
+ if (routedDescriptors.length === 0) return <>{fallback}</>;
366
+ return null;
367
+ }
368
+
369
+ export type InteractionPartProps = PrimitiveCommonProps &
370
+ HTMLAttributes<HTMLElement>;
371
+
372
+ export function InteractionLabel({ children, ...props }: InteractionPartProps) {
373
+ const { descriptor, interaction } = useInteractionPrimitiveContext();
374
+ return renderPrimitive("span", {
375
+ ...props,
376
+ "data-dreamboard-interaction-label": "",
377
+ children:
378
+ children ??
379
+ (descriptor
380
+ ? interactionLabel(descriptor)
381
+ : humanizeInteraction(interaction)),
382
+ });
383
+ }
384
+
385
+ export function InteractionDescription({
386
+ children,
387
+ ...props
388
+ }: InteractionPartProps) {
389
+ const content = children;
390
+ if (!content) return null;
391
+ return renderPrimitive("span", {
392
+ ...props,
393
+ "data-dreamboard-interaction-description": "",
394
+ children: content,
395
+ });
396
+ }
397
+
398
+ export function InteractionUnavailableMessage({
399
+ children,
400
+ ...props
401
+ }: InteractionPartProps) {
402
+ const { descriptor, handle } = useInteractionPrimitiveContext();
403
+ const reason =
404
+ children ??
405
+ handle?.unavailableReason ??
406
+ interactionUnavailableReason(descriptor);
407
+ if (!reason) return null;
408
+ return renderPrimitive("span", {
409
+ ...props,
410
+ "data-dreamboard-interaction-unavailable": "",
411
+ children: reason,
412
+ });
413
+ }
414
+
415
+ export function InteractionValidationMessage({
416
+ children,
417
+ ...props
418
+ }: InteractionPartProps) {
419
+ const { handle } = useInteractionPrimitiveContext();
420
+ const validation = handle?.validateDraft();
421
+ const message =
422
+ children ??
423
+ validation?.formErrors[0] ??
424
+ Object.values(validation?.fieldErrors ?? {})[0]?.[0] ??
425
+ (validation?.missing[0]
426
+ ? `${String(validation.missing[0])} is required.`
427
+ : null);
428
+ if (!message) return null;
429
+ return renderPrimitive("span", {
430
+ ...props,
431
+ "data-dreamboard-interaction-validation": "",
432
+ children: message,
433
+ });
434
+ }
435
+
436
+ export type InteractionTriggerProps = PrimitiveCommonProps &
437
+ ButtonHTMLAttributes<HTMLButtonElement>;
438
+
439
+ export function InteractionTrigger({
440
+ disabled,
441
+ onClick,
442
+ ...props
443
+ }: InteractionTriggerProps) {
444
+ const { descriptor, handle } = useInteractionPrimitiveContext();
445
+ const available = isInteractionAvailable(descriptor);
446
+ const isDisabled = disabled === true || !available;
447
+ return renderPrimitive("button", {
448
+ type: "button",
449
+ ...props,
450
+ disabled: isDisabled,
451
+ "aria-disabled": isDisabled,
452
+ "data-dreamboard-interaction-trigger": "",
453
+ "data-interaction-id": descriptor?.interactionId,
454
+ "data-interaction-key": descriptor?.interactionKey,
455
+ "data-available": available,
456
+ "data-disabled": isDisabled || undefined,
457
+ "data-state": handle?.isArmed ? "armed" : "idle",
458
+ onClick: composeEventHandlers(onClick, () => {
459
+ handle?.arm();
460
+ }),
461
+ });
462
+ }
463
+
464
+ export interface InteractionStateSnapshot<
465
+ Params extends InteractionParamsShape = InteractionParamsShape,
466
+ DefaultedKeys extends keyof Params & string = never,
467
+ > {
468
+ interaction: string;
469
+ descriptor: InteractionDescriptor;
470
+ handle: InteractionHandle<Params, DefaultedKeys>;
471
+ draft: InteractionHandle<Params, DefaultedKeys>["draft"];
472
+ values: InteractionHandle<Params, DefaultedKeys>["values"];
473
+ status: InteractionHandle<Params, DefaultedKeys>["status"];
474
+ available: boolean;
475
+ isReady: boolean;
476
+ isArmed: boolean;
477
+ inputKeys: readonly string[];
478
+ missingInputs: readonly string[];
479
+ readyFrontier: readonly string[];
480
+ blockedInputs: readonly string[];
481
+ hasInputs: boolean;
482
+ }
483
+
484
+ export interface InteractionStateProps<
485
+ Params extends InteractionParamsShape = InteractionParamsShape,
486
+ DefaultedKeys extends keyof Params & string = never,
487
+ > {
488
+ unavailable: ReactNode;
489
+ children: (
490
+ state: InteractionStateSnapshot<Params, DefaultedKeys>,
491
+ ) => ReactNode;
492
+ }
493
+
494
+ export function InteractionState<
495
+ Params extends InteractionParamsShape = InteractionParamsShape,
496
+ DefaultedKeys extends keyof Params & string = never,
497
+ >({ children, unavailable }: InteractionStateProps<Params, DefaultedKeys>) {
498
+ const { interaction, descriptor, handle } = useInteractionPrimitiveContext();
499
+ const store = useInteractionUiStore();
500
+ if (!descriptor || !handle) {
501
+ return <>{unavailable}</>;
502
+ }
503
+ const typedHandle = castInteractionHandle<Params, DefaultedKeys>(handle);
504
+ const liveDraft = castInteractionDraft<Params, DefaultedKeys>(
505
+ store.getDraft(descriptor.interactionKey),
506
+ );
507
+ const inputKeys = descriptor.inputs.map((input) => input.key);
508
+ const readiness = getInteractionDraftReadiness(descriptor, liveDraft);
509
+ return (
510
+ <>
511
+ {children({
512
+ interaction,
513
+ descriptor,
514
+ handle: typedHandle,
515
+ draft: liveDraft,
516
+ values: typedHandle.values,
517
+ status: typedHandle.status,
518
+ available: typedHandle.available,
519
+ isReady: readiness.ready,
520
+ isArmed: typedHandle.isArmed,
521
+ inputKeys,
522
+ missingInputs: readiness.missingInputs,
523
+ readyFrontier: readiness.readyFrontier,
524
+ blockedInputs: readiness.blockedInputs,
525
+ hasInputs: inputKeys.length > 0,
526
+ })}
527
+ </>
528
+ );
529
+ }
530
+
531
+ export type InteractionFormPrimitiveProps<
532
+ Params extends InteractionParamsShape = InteractionParamsShape,
533
+ DefaultedKeys extends keyof Params & string = never,
534
+ > = BoundInteractionFormProps<Params, DefaultedKeys>;
535
+
536
+ export function InteractionFormPrimitive<
537
+ Params extends InteractionParamsShape = InteractionParamsShape,
538
+ DefaultedKeys extends keyof Params & string = never,
539
+ >(props: InteractionFormPrimitiveProps<Params, DefaultedKeys>) {
540
+ const { descriptor, handle } = useInteractionPrimitiveContext();
541
+ if (!descriptor || !handle) return null;
542
+ return (
543
+ <BoundInteractionForm<Params, DefaultedKeys>
544
+ descriptor={descriptor}
545
+ handle={handle}
546
+ {...props}
547
+ />
548
+ );
549
+ }
550
+
551
+ export type InteractionFieldPrimitiveProps<
552
+ Params extends InteractionParamsShape = InteractionParamsShape,
553
+ Input extends keyof Params & string = keyof Params & string,
554
+ > = Omit<
555
+ BaseInteractionFieldProps<Params, Input>,
556
+ "descriptor" | "handle" | "inputKey"
557
+ > & {
558
+ input: Input;
559
+ };
560
+
561
+ export function InteractionFieldPrimitive<
562
+ Params extends InteractionParamsShape = InteractionParamsShape,
563
+ Input extends keyof Params & string = keyof Params & string,
564
+ >({ input, ...props }: InteractionFieldPrimitiveProps<Params, Input>) {
565
+ const { descriptor, handle } = useInteractionPrimitiveContext();
566
+ if (!descriptor || !handle) return null;
567
+ return (
568
+ <BaseInteractionField<Params, Input>
569
+ descriptor={descriptor}
570
+ handle={castInteractionHandle<Params>(handle)}
571
+ inputKey={input}
572
+ {...props}
573
+ />
574
+ );
575
+ }
576
+
577
+ export type InteractionSubmitProps = PrimitiveCommonProps &
578
+ ButtonHTMLAttributes<HTMLButtonElement> & {
579
+ params?:
580
+ | InteractionParamsShape
581
+ | (() => InteractionParamsShape | null | undefined);
582
+ onSubmitSuccess?: InteractionSubmitCallbacks["onSubmitSuccess"];
583
+ onSubmitError?: InteractionSubmitCallbacks["onSubmitError"];
584
+ };
585
+
586
+ export function InteractionSubmit({
587
+ disabled,
588
+ onClick,
589
+ params,
590
+ onSubmitSuccess,
591
+ onSubmitError,
592
+ ...props
593
+ }: InteractionSubmitProps) {
594
+ const { descriptor, handle } = useInteractionPrimitiveContext();
595
+ const gameActionError = useGameActionError();
596
+ const isSubmitting = handle?.status === "submitting";
597
+ const hasExplicitParams = params !== undefined;
598
+ const available = isInteractionAvailable(descriptor);
599
+ const isDisabled =
600
+ disabled === true ||
601
+ !available ||
602
+ (!hasExplicitParams && !handle?.isReady) ||
603
+ isSubmitting;
604
+ return renderPrimitive("button", {
605
+ type: "button",
606
+ ...props,
607
+ disabled: isDisabled,
608
+ "aria-disabled": isDisabled,
609
+ "data-dreamboard-interaction-submit": "",
610
+ "data-interaction-id": descriptor?.interactionId,
611
+ "data-interaction-key": descriptor?.interactionKey,
612
+ "data-available": available,
613
+ "data-disabled": isDisabled || undefined,
614
+ "data-ready": handle?.isReady ?? false,
615
+ "data-has-inputs": descriptor ? descriptor.inputs.length > 0 : undefined,
616
+ "data-input-count": descriptor?.inputs.length,
617
+ "data-submitting": isSubmitting || undefined,
618
+ "data-state": handle?.status ?? "unavailable",
619
+ onClick: composeEventHandlers(onClick, () => {
620
+ if (isDisabled || !handle) return;
621
+ const resolvedParams = typeof params === "function" ? params() : params;
622
+ if (resolvedParams === null || resolvedParams === undefined) {
623
+ void submitInteractionDraft(handle, {
624
+ onSubmitSuccess,
625
+ onSubmitError: onSubmitError ?? gameActionError ?? undefined,
626
+ });
627
+ return;
628
+ }
629
+ void submitInteractionParams(handle, resolvedParams, {
630
+ onSubmitSuccess,
631
+ onSubmitError: onSubmitError ?? gameActionError ?? undefined,
632
+ });
633
+ }),
634
+ });
635
+ }
636
+
637
+ export type InteractionInputProps = PrimitiveCommonProps &
638
+ Omit<InputHTMLAttributes<HTMLInputElement>, "name"> & {
639
+ name: string;
640
+ parse?: (value: string) => unknown;
641
+ };
642
+
643
+ export function InteractionInput({
644
+ name,
645
+ parse,
646
+ onChange,
647
+ disabled,
648
+ ...props
649
+ }: InteractionInputProps) {
650
+ const { descriptor, handle } = useInteractionPrimitiveContext();
651
+ const value = handle?.draft[name];
652
+ const isDisabled = disabled === true || !isInteractionAvailable(descriptor);
653
+ return renderPrimitive("input", {
654
+ ...props,
655
+ name,
656
+ disabled: isDisabled,
657
+ "aria-disabled": isDisabled,
658
+ "data-dreamboard-interaction-input": "",
659
+ "data-input-name": name,
660
+ "data-disabled": isDisabled || undefined,
661
+ "data-selected": value !== undefined || undefined,
662
+ onChange: composeEventHandlers(onChange, (event) => {
663
+ const target = event.currentTarget;
664
+ handle?.setInput(name, parse ? parse(target.value) : target.value);
665
+ }),
666
+ });
667
+ }
668
+
669
+ export type InteractionCardInputProps<
670
+ Input extends string = InteractionInputKey,
671
+ > = PrimitiveCommonProps &
672
+ Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
673
+ input: Input;
674
+ unsafeCardId?: string;
675
+ selected?: boolean;
676
+ onSelectedChange?: (selected: boolean) => void;
677
+ children?:
678
+ | ReactNode
679
+ | ((state: InteractionCardInputRenderState) => ReactNode);
680
+ };
681
+
682
+ export interface InteractionCardInputRenderState {
683
+ cardId: string | undefined;
684
+ zone: string | undefined;
685
+ selected: boolean;
686
+ disabled: boolean;
687
+ eligible: boolean;
688
+ targetValid: boolean;
689
+ selectable: boolean;
690
+ cardAvailable: boolean;
691
+ invalid: boolean;
692
+ }
693
+
694
+ export function InteractionCardInput<
695
+ Input extends string = InteractionInputKey,
696
+ >({
697
+ input,
698
+ unsafeCardId,
699
+ selected,
700
+ onSelectedChange,
701
+ onClick,
702
+ disabled,
703
+ children,
704
+ ...props
705
+ }: InteractionCardInputProps<Input>) {
706
+ const { descriptor, handle } = useInteractionPrimitiveContext();
707
+ const store = useInteractionUiStore();
708
+ const zoneCard = useZoneCardContext();
709
+ const zoneContext = useOptionalZonePrimitiveContext();
710
+ const cardId = zoneCard?.cardId ?? unsafeCardId;
711
+ const validationZone = zoneCard?.zone ?? zoneContext?.zone;
712
+ const zoneSnapshot = usePluginState((state) =>
713
+ validationZone ? state.gameplay.zones[validationZone] : undefined,
714
+ );
715
+ const cardDescriptor = usePluginState((state) => {
716
+ if (!cardId || !validationZone) return undefined;
717
+ return state.gameplay.zones[validationZone]?.playableByCardId[cardId]?.find(
718
+ (candidate) =>
719
+ candidate.interactionKey === descriptor?.interactionKey &&
720
+ candidate.inputs.some((candidateInput) => candidateInput.key === input),
721
+ );
722
+ });
723
+ const inputDescriptor = descriptor
724
+ ? inputByKey(descriptor, input)
725
+ : undefined;
726
+ const targetInvalidReason = cardTargetInvalidReason({
727
+ cardDescriptor,
728
+ cardId,
729
+ inputDescriptor,
730
+ unsafeCardId,
731
+ validationZone,
732
+ zoneCard,
733
+ zoneSnapshot,
734
+ });
735
+ const isTargetValid = targetInvalidReason === undefined;
736
+ throwCardInputDevMismatch({
737
+ cardId,
738
+ targetInvalidReason,
739
+ unsafeCardId,
740
+ validationZone,
741
+ zoneCard,
742
+ });
743
+ const liveDraft = descriptor ? store.getDraft(descriptor.interactionKey) : {};
744
+ const currentValue =
745
+ liveDraft[input] ?? handle?.draft[input] ?? handle?.values[input];
746
+ const selection = isTargetDomain(inputDescriptor?.domain)
747
+ ? inputDescriptor.domain.selection
748
+ : undefined;
749
+ const selectedByDraft =
750
+ selection?.mode === "many"
751
+ ? Array.isArray(currentValue) &&
752
+ currentValue.map((item) => String(item)).includes(String(cardId))
753
+ : currentValue !== undefined &&
754
+ cardId !== undefined &&
755
+ String(currentValue) === String(cardId);
756
+ const isSelected = selected ?? selectedByDraft;
757
+ const descriptorAvailable = isInteractionAvailable(descriptor);
758
+ const cardDescriptorAvailable =
759
+ cardDescriptor === undefined
760
+ ? undefined
761
+ : isInteractionAvailable(cardDescriptor);
762
+ const isCardAvailable =
763
+ cardDescriptorAvailable ?? (isTargetValid && descriptorAvailable);
764
+ const cardUnavailableReason =
765
+ interactionUnavailableReason(cardDescriptor) ??
766
+ (!descriptorAvailable
767
+ ? (interactionUnavailableReason(descriptor) ?? "interaction-unavailable")
768
+ : undefined);
769
+ const isSelectable =
770
+ cardId !== undefined &&
771
+ inputDescriptor !== undefined &&
772
+ isManyTargetSelectable(inputDescriptor, currentValue, cardId);
773
+ const validation = handle?.validateDraft();
774
+ const fieldErrors = validation?.fieldErrors[input] ?? [];
775
+ const isInvalid = fieldErrors.length > 0;
776
+ const isDisabled =
777
+ disabled === true ||
778
+ !descriptorAvailable ||
779
+ !cardId ||
780
+ !isTargetValid ||
781
+ !isCardAvailable ||
782
+ !isSelectable ||
783
+ !handle;
784
+
785
+ const activateCardInput = useCallback(() => {
786
+ if (isDisabled || !descriptor || !cardId || !handle || !inputDescriptor) {
787
+ return;
788
+ }
789
+ const { params, readiness } = routeCardInputIntent(store, descriptor, {
790
+ cardInputKey: input,
791
+ cardId,
792
+ });
793
+ const hasMissingSurfaceTarget = readiness.readyFrontier.some((key) => {
794
+ const candidate = inputByKey(descriptor, key);
795
+ return (
796
+ candidate !== undefined &&
797
+ isBoardTargetDomain(candidate.domain) &&
798
+ candidate.domain.selection?.mode !== "many"
799
+ );
800
+ });
801
+ const hasMissingFormInput = readiness.readyFrontier.some((key) => {
802
+ const candidate = inputByKey(descriptor, key);
803
+ return (
804
+ candidate !== undefined &&
805
+ (!isTargetDomain(candidate.domain) ||
806
+ candidate.domain.selection?.mode === "many")
807
+ );
808
+ });
809
+ const hasFieldErrors = hasInteractionFieldErrors(readiness.fieldErrors);
810
+ if (shouldRouteInteractionPending(descriptor, readiness)) {
811
+ markInteractionPending(store, descriptor);
812
+ store.setPendingInteraction(
813
+ !hasMissingSurfaceTarget && (hasMissingFormInput || hasFieldErrors)
814
+ ? descriptor.interactionKey
815
+ : null,
816
+ );
817
+ }
818
+ onSelectedChange?.(
819
+ selection?.mode === "many"
820
+ ? Array.isArray(params[input]) && params[input].includes(cardId)
821
+ : true,
822
+ );
823
+ }, [
824
+ cardId,
825
+ descriptor,
826
+ handle,
827
+ input,
828
+ inputDescriptor,
829
+ isDisabled,
830
+ onSelectedChange,
831
+ selection,
832
+ store,
833
+ ]);
834
+
835
+ const renderState: InteractionCardInputRenderState = {
836
+ cardId,
837
+ zone: validationZone,
838
+ selected: isSelected,
839
+ disabled: isDisabled,
840
+ eligible: isTargetValid && isCardAvailable,
841
+ targetValid: isTargetValid,
842
+ selectable: isSelectable,
843
+ cardAvailable: isCardAvailable,
844
+ invalid: isInvalid,
845
+ };
846
+ const renderedChildren =
847
+ typeof children === "function" ? children(renderState) : children;
848
+
849
+ return renderPrimitive("button", {
850
+ type: "button",
851
+ ...props,
852
+ children: renderedChildren,
853
+ disabled: isDisabled,
854
+ "aria-disabled": isDisabled,
855
+ "aria-pressed": isSelected,
856
+ "data-dreamboard-interaction-card-input": "",
857
+ "data-input-name": input,
858
+ "data-card-id": cardId,
859
+ "data-zone": validationZone,
860
+ "data-selected": isSelected || undefined,
861
+ "data-eligible": isTargetValid && isCardAvailable,
862
+ "data-target-valid": isTargetValid,
863
+ "data-target-invalid-reason": targetInvalidReason,
864
+ "data-selectable": isSelectable,
865
+ "data-card-available": isCardAvailable,
866
+ "data-card-unavailable-reason": cardUnavailableReason,
867
+ "data-invalid": isInvalid || undefined,
868
+ "data-disabled": isDisabled || undefined,
869
+ "data-missing-resource": cardId ? undefined : true,
870
+ onClick: composeEventHandlers(onClick, () => {
871
+ activateCardInput();
872
+ }),
873
+ });
874
+ }
875
+
876
+ type CardTargetInvalidReason =
877
+ | "missing-card"
878
+ | "wrong-zone"
879
+ | "not-in-zone"
880
+ | "not-top-card"
881
+ | "not-in-domain";
882
+
883
+ function cardTargetInvalidReason({
884
+ cardDescriptor,
885
+ cardId,
886
+ inputDescriptor,
887
+ unsafeCardId,
888
+ validationZone,
889
+ zoneCard,
890
+ zoneSnapshot,
891
+ }: {
892
+ cardDescriptor: InteractionDescriptor | undefined;
893
+ cardId: string | undefined;
894
+ inputDescriptor: InteractionInputDescriptor | undefined;
895
+ unsafeCardId: string | undefined;
896
+ validationZone: string | undefined;
897
+ zoneCard: ReturnType<typeof useZoneCardContext>;
898
+ zoneSnapshot: ZoneHandlesSnapshot | undefined;
899
+ }): CardTargetInvalidReason | undefined {
900
+ if (!cardId) return "missing-card";
901
+ if (inputDescriptor?.domain.type !== "cardTarget") {
902
+ return "not-in-domain";
903
+ }
904
+ if (zoneCard && unsafeCardId && unsafeCardId !== zoneCard.cardId) {
905
+ return "wrong-zone";
906
+ }
907
+ if (
908
+ validationZone &&
909
+ zoneSnapshot &&
910
+ !zoneSnapshot.cardIds.includes(cardId)
911
+ ) {
912
+ return "not-in-zone";
913
+ }
914
+ if (
915
+ validationZone &&
916
+ inputDescriptor.domain.zoneIds &&
917
+ !inputDescriptor.domain.zoneIds.includes(validationZone)
918
+ ) {
919
+ return "wrong-zone";
920
+ }
921
+ if (validationZone && !cardDescriptor) {
922
+ return zoneSnapshot?.cardIds[0] !== cardId
923
+ ? "not-top-card"
924
+ : "not-in-domain";
925
+ }
926
+ if (
927
+ !zoneCard &&
928
+ isResolvedTargetDomain(inputDescriptor.domain) &&
929
+ !inputDescriptor.domain.eligibleTargets.includes(cardId)
930
+ ) {
931
+ return "not-in-domain";
932
+ }
933
+ return undefined;
934
+ }
935
+
936
+ function throwCardInputDevMismatch({
937
+ cardId,
938
+ targetInvalidReason,
939
+ unsafeCardId,
940
+ validationZone,
941
+ zoneCard,
942
+ }: {
943
+ cardId: string | undefined;
944
+ targetInvalidReason: CardTargetInvalidReason | undefined;
945
+ unsafeCardId: string | undefined;
946
+ validationZone: string | undefined;
947
+ zoneCard: ReturnType<typeof useZoneCardContext>;
948
+ }) {
949
+ if (!isDevelopmentRuntime() || !validationZone) return;
950
+ if (zoneCard && unsafeCardId && unsafeCardId !== zoneCard.cardId) {
951
+ throw new Error(
952
+ `Interaction.CardInput unsafeCardId '${unsafeCardId}' does not match surrounding Zone.Item card '${zoneCard.cardId}'.`,
953
+ );
954
+ }
955
+ if (targetInvalidReason === "not-in-zone" && cardId) {
956
+ throw new Error(
957
+ `Interaction.CardInput card '${cardId}' is not present in surrounding zone '${validationZone}'.`,
958
+ );
959
+ }
960
+ }
961
+
962
+ function isDevelopmentRuntime(): boolean {
963
+ const processLike = (
964
+ globalThis as {
965
+ process?: { env?: { NODE_ENV?: string } };
966
+ }
967
+ ).process;
968
+ return processLike?.env?.NODE_ENV !== "production";
969
+ }
970
+
971
+ export const Interaction = {
972
+ Root: InteractionRoot,
973
+ State: InteractionState,
974
+ Switch: InteractionSwitch,
975
+ Routes: InteractionRoutes,
976
+ Dialog: InteractionDialog,
977
+ Trigger: InteractionTrigger,
978
+ Label: InteractionLabel,
979
+ Description: InteractionDescription,
980
+ UnavailableMessage: InteractionUnavailableMessage,
981
+ ValidationMessage: InteractionValidationMessage,
982
+ Input: InteractionInput,
983
+ Field: InteractionFieldPrimitive,
984
+ CardInput: InteractionCardInput,
985
+ Form: InteractionFormPrimitive,
986
+ Submit: InteractionSubmit,
987
+ };