@datamoc/mw_games 0.7.6 → 0.7.8

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 (367) hide show
  1. package/CHANGELOG.md +359 -0
  2. package/README.md +7 -7
  3. package/dist/actors/Inventory.d.ts +7 -1
  4. package/dist/actors/Inventory.d.ts.map +1 -1
  5. package/dist/actors/Inventory.js.map +1 -1
  6. package/dist/actors/Shop.d.ts.map +1 -1
  7. package/dist/actors/Shop.js +4 -1
  8. package/dist/actors/Shop.js.map +1 -1
  9. package/dist/actors/craft.d.ts +35 -14
  10. package/dist/actors/craft.d.ts.map +1 -1
  11. package/dist/actors/craft.js +56 -24
  12. package/dist/actors/craft.js.map +1 -1
  13. package/dist/actors/index.d.ts +1 -1
  14. package/dist/actors/index.d.ts.map +1 -1
  15. package/dist/ai/Heuristics.d.ts +240 -0
  16. package/dist/ai/Heuristics.d.ts.map +1 -0
  17. package/dist/ai/Heuristics.js +298 -0
  18. package/dist/ai/Heuristics.js.map +1 -0
  19. package/dist/ai/index.d.ts +4 -0
  20. package/dist/ai/index.d.ts.map +1 -1
  21. package/dist/ai/index.js +2 -0
  22. package/dist/ai/index.js.map +1 -1
  23. package/dist/ai/lua.d.ts +21 -0
  24. package/dist/ai/lua.d.ts.map +1 -1
  25. package/dist/ai/lua.js +21 -0
  26. package/dist/ai/lua.js.map +1 -1
  27. package/dist/ai/score.d.ts +162 -0
  28. package/dist/ai/score.d.ts.map +1 -0
  29. package/dist/ai/score.js +147 -0
  30. package/dist/ai/score.js.map +1 -0
  31. package/dist/assets/loader.d.ts +18 -4
  32. package/dist/assets/loader.d.ts.map +1 -1
  33. package/dist/assets/loader.js +40 -11
  34. package/dist/assets/loader.js.map +1 -1
  35. package/dist/audio/Positional.d.ts +125 -0
  36. package/dist/audio/Positional.d.ts.map +1 -0
  37. package/dist/audio/Positional.js +135 -0
  38. package/dist/audio/Positional.js.map +1 -0
  39. package/dist/audio/Sound.d.ts +7 -1
  40. package/dist/audio/Sound.d.ts.map +1 -1
  41. package/dist/audio/Sound.js +8 -2
  42. package/dist/audio/Sound.js.map +1 -1
  43. package/dist/audio/index.d.ts +2 -0
  44. package/dist/audio/index.d.ts.map +1 -1
  45. package/dist/audio/index.js +1 -0
  46. package/dist/audio/index.js.map +1 -1
  47. package/dist/battle/AttackDialog.d.ts +75 -0
  48. package/dist/battle/AttackDialog.d.ts.map +1 -0
  49. package/dist/battle/AttackDialog.js +115 -0
  50. package/dist/battle/AttackDialog.js.map +1 -0
  51. package/dist/battle/AttackPreview.d.ts +70 -0
  52. package/dist/battle/AttackPreview.d.ts.map +1 -0
  53. package/dist/battle/AttackPreview.js +78 -0
  54. package/dist/battle/AttackPreview.js.map +1 -0
  55. package/dist/battle/BattleStats.d.ts +45 -0
  56. package/dist/battle/BattleStats.d.ts.map +1 -0
  57. package/dist/battle/BattleStats.js +63 -0
  58. package/dist/battle/BattleStats.js.map +1 -0
  59. package/dist/battle/UnitSelector.d.ts +72 -0
  60. package/dist/battle/UnitSelector.d.ts.map +1 -0
  61. package/dist/battle/UnitSelector.js +117 -0
  62. package/dist/battle/UnitSelector.js.map +1 -0
  63. package/dist/battle/Whiteboard.d.ts +50 -0
  64. package/dist/battle/Whiteboard.d.ts.map +1 -0
  65. package/dist/battle/Whiteboard.js +94 -0
  66. package/dist/battle/Whiteboard.js.map +1 -0
  67. package/dist/battle/index.d.ts +10 -0
  68. package/dist/battle/index.d.ts.map +1 -1
  69. package/dist/battle/index.js +5 -0
  70. package/dist/battle/index.js.map +1 -1
  71. package/dist/board/Classics.d.ts.map +1 -1
  72. package/dist/board/Classics.js +51 -39
  73. package/dist/board/Classics.js.map +1 -1
  74. package/dist/board/FogOfWar.d.ts +32 -2
  75. package/dist/board/FogOfWar.d.ts.map +1 -1
  76. package/dist/board/FogOfWar.js +70 -6
  77. package/dist/board/FogOfWar.js.map +1 -1
  78. package/dist/board/HexSkirmish.d.ts.map +1 -1
  79. package/dist/board/HexSkirmish.js +69 -20
  80. package/dist/board/HexSkirmish.js.map +1 -1
  81. package/dist/board/Tactics.d.ts +7 -0
  82. package/dist/board/Tactics.d.ts.map +1 -1
  83. package/dist/board/Tactics.js +2 -2
  84. package/dist/board/Tactics.js.map +1 -1
  85. package/dist/core/Achievements.d.ts +40 -7
  86. package/dist/core/Achievements.d.ts.map +1 -1
  87. package/dist/core/Achievements.js +53 -7
  88. package/dist/core/Achievements.js.map +1 -1
  89. package/dist/core/Hex.d.ts +26 -6
  90. package/dist/core/Hex.d.ts.map +1 -1
  91. package/dist/core/Hex.js +40 -13
  92. package/dist/core/Hex.js.map +1 -1
  93. package/dist/core/Input.d.ts +36 -0
  94. package/dist/core/Input.d.ts.map +1 -1
  95. package/dist/core/Input.js +63 -1
  96. package/dist/core/Input.js.map +1 -1
  97. package/dist/core/Random.d.ts +93 -0
  98. package/dist/core/Random.d.ts.map +1 -1
  99. package/dist/core/Random.js +177 -0
  100. package/dist/core/Random.js.map +1 -1
  101. package/dist/core/SyncGuard.d.ts +57 -0
  102. package/dist/core/SyncGuard.d.ts.map +1 -0
  103. package/dist/core/SyncGuard.js +116 -0
  104. package/dist/core/SyncGuard.js.map +1 -0
  105. package/dist/core/index.d.ts +3 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +2 -1
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/i18n/Po.d.ts +51 -0
  110. package/dist/i18n/Po.d.ts.map +1 -0
  111. package/dist/i18n/Po.js +176 -0
  112. package/dist/i18n/Po.js.map +1 -0
  113. package/dist/i18n/index.d.ts +2 -0
  114. package/dist/i18n/index.d.ts.map +1 -1
  115. package/dist/i18n/index.js +1 -0
  116. package/dist/i18n/index.js.map +1 -1
  117. package/dist/mw_games.global.js +104 -98
  118. package/dist/mwl/MapFile.d.ts +41 -0
  119. package/dist/mwl/MapFile.d.ts.map +1 -0
  120. package/dist/mwl/MapFile.js +43 -0
  121. package/dist/mwl/MapFile.js.map +1 -0
  122. package/dist/mwl/actors.d.ts +46 -2
  123. package/dist/mwl/actors.d.ts.map +1 -1
  124. package/dist/mwl/actors.js +46 -2
  125. package/dist/mwl/actors.js.map +1 -1
  126. package/dist/mwl/campaign.d.ts +51 -0
  127. package/dist/mwl/campaign.d.ts.map +1 -0
  128. package/dist/mwl/campaign.js +58 -0
  129. package/dist/mwl/campaign.js.map +1 -0
  130. package/dist/mwl/carryover.d.ts +86 -0
  131. package/dist/mwl/carryover.d.ts.map +1 -0
  132. package/dist/mwl/carryover.js +70 -0
  133. package/dist/mwl/carryover.js.map +1 -0
  134. package/dist/mwl/catalog.d.ts +10 -1
  135. package/dist/mwl/catalog.d.ts.map +1 -1
  136. package/dist/mwl/catalog.js +10 -1
  137. package/dist/mwl/catalog.js.map +1 -1
  138. package/dist/mwl/compiler.d.ts +70 -3
  139. package/dist/mwl/compiler.d.ts.map +1 -1
  140. package/dist/mwl/compiler.js +70 -3
  141. package/dist/mwl/compiler.js.map +1 -1
  142. package/dist/mwl/conditions.d.ts +10 -1
  143. package/dist/mwl/conditions.d.ts.map +1 -1
  144. package/dist/mwl/conditions.js +10 -1
  145. package/dist/mwl/conditions.js.map +1 -1
  146. package/dist/mwl/content.d.ts +21 -0
  147. package/dist/mwl/content.d.ts.map +1 -1
  148. package/dist/mwl/content.js +19 -0
  149. package/dist/mwl/content.js.map +1 -1
  150. package/dist/mwl/expression.d.ts +21 -0
  151. package/dist/mwl/expression.d.ts.map +1 -1
  152. package/dist/mwl/expression.js +21 -0
  153. package/dist/mwl/expression.js.map +1 -1
  154. package/dist/mwl/fengari.d.ts +12 -1
  155. package/dist/mwl/fengari.d.ts.map +1 -1
  156. package/dist/mwl/fengari.js +12 -1
  157. package/dist/mwl/fengari.js.map +1 -1
  158. package/dist/mwl/grammar.d.ts +45 -1
  159. package/dist/mwl/grammar.d.ts.map +1 -1
  160. package/dist/mwl/grammar.js +45 -1
  161. package/dist/mwl/grammar.js.map +1 -1
  162. package/dist/mwl/hooks.d.ts +51 -6
  163. package/dist/mwl/hooks.d.ts.map +1 -1
  164. package/dist/mwl/hooks.js +45 -1
  165. package/dist/mwl/hooks.js.map +1 -1
  166. package/dist/mwl/index.d.ts +9 -3
  167. package/dist/mwl/index.d.ts.map +1 -1
  168. package/dist/mwl/index.js +5 -2
  169. package/dist/mwl/index.js.map +1 -1
  170. package/dist/mwl/persistence.d.ts +32 -0
  171. package/dist/mwl/persistence.d.ts.map +1 -1
  172. package/dist/mwl/persistence.js +32 -0
  173. package/dist/mwl/persistence.js.map +1 -1
  174. package/dist/mwl/readers.d.ts +24 -2
  175. package/dist/mwl/readers.d.ts.map +1 -1
  176. package/dist/mwl/readers.js +27 -4
  177. package/dist/mwl/readers.js.map +1 -1
  178. package/dist/mwl/report.d.ts +21 -2
  179. package/dist/mwl/report.d.ts.map +1 -1
  180. package/dist/mwl/report.js +21 -2
  181. package/dist/mwl/report.js.map +1 -1
  182. package/dist/mwl/runtime.d.ts +212 -7
  183. package/dist/mwl/runtime.d.ts.map +1 -1
  184. package/dist/mwl/runtime.js +682 -90
  185. package/dist/mwl/runtime.js.map +1 -1
  186. package/dist/mwl/schema.d.ts +57 -1
  187. package/dist/mwl/schema.d.ts.map +1 -1
  188. package/dist/mwl/schema.js +181 -21
  189. package/dist/mwl/schema.js.map +1 -1
  190. package/dist/mwl/scripts.d.ts +12 -1
  191. package/dist/mwl/scripts.d.ts.map +1 -1
  192. package/dist/mwl/scripts.js +12 -1
  193. package/dist/mwl/scripts.js.map +1 -1
  194. package/dist/mwl/utils.d.ts +2 -0
  195. package/dist/mwl/utils.d.ts.map +1 -1
  196. package/dist/mwl/utils.js +4 -1
  197. package/dist/mwl/utils.js.map +1 -1
  198. package/dist/roguelike/MultiTurnBeam.d.ts +50 -12
  199. package/dist/roguelike/MultiTurnBeam.d.ts.map +1 -1
  200. package/dist/roguelike/MultiTurnBeam.js +100 -53
  201. package/dist/roguelike/MultiTurnBeam.js.map +1 -1
  202. package/dist/roguelike/Targeting.d.ts +94 -0
  203. package/dist/roguelike/Targeting.d.ts.map +1 -1
  204. package/dist/roguelike/Targeting.js +123 -0
  205. package/dist/roguelike/Targeting.js.map +1 -1
  206. package/dist/roguelike/index.d.ts +3 -1
  207. package/dist/roguelike/index.d.ts.map +1 -1
  208. package/dist/roguelike/index.js +1 -0
  209. package/dist/roguelike/index.js.map +1 -1
  210. package/dist/rpg/GameState.d.ts +2 -2
  211. package/dist/rpg/GameState.js +2 -2
  212. package/dist/simulation/CampaignSave.d.ts +68 -0
  213. package/dist/simulation/CampaignSave.d.ts.map +1 -0
  214. package/dist/simulation/CampaignSave.js +54 -0
  215. package/dist/simulation/CampaignSave.js.map +1 -0
  216. package/dist/simulation/EventPresentation.d.ts +111 -0
  217. package/dist/simulation/EventPresentation.d.ts.map +1 -0
  218. package/dist/simulation/EventPresentation.js +120 -0
  219. package/dist/simulation/EventPresentation.js.map +1 -0
  220. package/dist/simulation/index.d.ts +4 -0
  221. package/dist/simulation/index.d.ts.map +1 -1
  222. package/dist/simulation/index.js +2 -0
  223. package/dist/simulation/index.js.map +1 -1
  224. package/dist/two-d/pixi-interop.d.ts +18 -6
  225. package/dist/two-d/pixi-interop.d.ts.map +1 -1
  226. package/dist/two-d/pixi-interop.js +18 -6
  227. package/dist/two-d/pixi-interop.js.map +1 -1
  228. package/dist/two-d/render/AnimatedSprite.d.ts +74 -7
  229. package/dist/two-d/render/AnimatedSprite.d.ts.map +1 -1
  230. package/dist/two-d/render/AnimatedSprite.js +88 -26
  231. package/dist/two-d/render/AnimatedSprite.js.map +1 -1
  232. package/dist/two-d/render/Camera.d.ts +64 -4
  233. package/dist/two-d/render/Camera.d.ts.map +1 -1
  234. package/dist/two-d/render/Camera.js +132 -11
  235. package/dist/two-d/render/Camera.js.map +1 -1
  236. package/dist/two-d/render/ColorTransformBatcher.d.ts +7 -1
  237. package/dist/two-d/render/ColorTransformBatcher.d.ts.map +1 -1
  238. package/dist/two-d/render/ColorTransformBatcher.js +10 -1
  239. package/dist/two-d/render/ColorTransformBatcher.js.map +1 -1
  240. package/dist/two-d/render/Halo.d.ts +58 -0
  241. package/dist/two-d/render/Halo.d.ts.map +1 -0
  242. package/dist/two-d/render/Halo.js +48 -0
  243. package/dist/two-d/render/Halo.js.map +1 -0
  244. package/dist/two-d/render/ImageModifiers.d.ts +174 -0
  245. package/dist/two-d/render/ImageModifiers.d.ts.map +1 -1
  246. package/dist/two-d/render/ImageModifiers.js +454 -1
  247. package/dist/two-d/render/ImageModifiers.js.map +1 -1
  248. package/dist/two-d/render/LightningArc.d.ts +57 -0
  249. package/dist/two-d/render/LightningArc.d.ts.map +1 -0
  250. package/dist/two-d/render/LightningArc.js +91 -0
  251. package/dist/two-d/render/LightningArc.js.map +1 -0
  252. package/dist/two-d/render/PaletteRemap.d.ts +135 -0
  253. package/dist/two-d/render/PaletteRemap.d.ts.map +1 -0
  254. package/dist/two-d/render/PaletteRemap.js +181 -0
  255. package/dist/two-d/render/PaletteRemap.js.map +1 -0
  256. package/dist/two-d/render/Particles.d.ts +26 -0
  257. package/dist/two-d/render/Particles.d.ts.map +1 -1
  258. package/dist/two-d/render/Particles.js +23 -2
  259. package/dist/two-d/render/Particles.js.map +1 -1
  260. package/dist/two-d/render/SpriteAttachment.d.ts +55 -0
  261. package/dist/two-d/render/SpriteAttachment.d.ts.map +1 -0
  262. package/dist/two-d/render/SpriteAttachment.js +60 -0
  263. package/dist/two-d/render/SpriteAttachment.js.map +1 -0
  264. package/dist/two-d/render/TerrainGraphics.d.ts +142 -0
  265. package/dist/two-d/render/TerrainGraphics.d.ts.map +1 -0
  266. package/dist/two-d/render/TerrainGraphics.js +161 -0
  267. package/dist/two-d/render/TerrainGraphics.js.map +1 -0
  268. package/dist/two-d/render/TerrainGraphicsLayer.d.ts +55 -0
  269. package/dist/two-d/render/TerrainGraphicsLayer.d.ts.map +1 -0
  270. package/dist/two-d/render/TerrainGraphicsLayer.js +54 -0
  271. package/dist/two-d/render/TerrainGraphicsLayer.js.map +1 -0
  272. package/dist/two-d/render/TintedSprite.js +2 -2
  273. package/dist/two-d/render/TintedSprite.js.map +1 -1
  274. package/dist/two-d/render/index.d.ts +15 -3
  275. package/dist/two-d/render/index.d.ts.map +1 -1
  276. package/dist/two-d/render/index.js +7 -1
  277. package/dist/two-d/render/index.js.map +1 -1
  278. package/dist/two-d/stage/StoryScreen.d.ts +110 -0
  279. package/dist/two-d/stage/StoryScreen.d.ts.map +1 -0
  280. package/dist/two-d/stage/StoryScreen.js +178 -0
  281. package/dist/two-d/stage/StoryScreen.js.map +1 -0
  282. package/dist/two-d/stage/index.d.ts +2 -0
  283. package/dist/two-d/stage/index.d.ts.map +1 -1
  284. package/dist/two-d/stage/index.js +1 -0
  285. package/dist/two-d/stage/index.js.map +1 -1
  286. package/dist/two-d/ui/Checkbox.d.ts +44 -0
  287. package/dist/two-d/ui/Checkbox.d.ts.map +1 -0
  288. package/dist/two-d/ui/Checkbox.js +89 -0
  289. package/dist/two-d/ui/Checkbox.js.map +1 -0
  290. package/dist/two-d/ui/DataTable.d.ts +79 -0
  291. package/dist/two-d/ui/DataTable.d.ts.map +1 -0
  292. package/dist/two-d/ui/DataTable.js +164 -0
  293. package/dist/two-d/ui/DataTable.js.map +1 -0
  294. package/dist/two-d/ui/Dropdown.d.ts +72 -0
  295. package/dist/two-d/ui/Dropdown.d.ts.map +1 -0
  296. package/dist/two-d/ui/Dropdown.js +126 -0
  297. package/dist/two-d/ui/Dropdown.js.map +1 -0
  298. package/dist/two-d/ui/FloatingText.d.ts +43 -4
  299. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  300. package/dist/two-d/ui/FloatingText.js +52 -5
  301. package/dist/two-d/ui/FloatingText.js.map +1 -1
  302. package/dist/two-d/ui/FloatingTextStack.d.ts +86 -13
  303. package/dist/two-d/ui/FloatingTextStack.d.ts.map +1 -1
  304. package/dist/two-d/ui/FloatingTextStack.js +89 -18
  305. package/dist/two-d/ui/FloatingTextStack.js.map +1 -1
  306. package/dist/two-d/ui/Layout.d.ts +105 -0
  307. package/dist/two-d/ui/Layout.d.ts.map +1 -0
  308. package/dist/two-d/ui/Layout.js +174 -0
  309. package/dist/two-d/ui/Layout.js.map +1 -0
  310. package/dist/two-d/ui/MarkupText.d.ts +53 -0
  311. package/dist/two-d/ui/MarkupText.d.ts.map +1 -0
  312. package/dist/two-d/ui/MarkupText.js +122 -0
  313. package/dist/two-d/ui/MarkupText.js.map +1 -0
  314. package/dist/two-d/ui/ScrollBox.d.ts +67 -0
  315. package/dist/two-d/ui/ScrollBox.d.ts.map +1 -0
  316. package/dist/two-d/ui/ScrollBox.js +127 -0
  317. package/dist/two-d/ui/ScrollBox.js.map +1 -0
  318. package/dist/two-d/ui/Skins.d.ts +59 -0
  319. package/dist/two-d/ui/Skins.d.ts.map +1 -0
  320. package/dist/two-d/ui/Skins.js +77 -0
  321. package/dist/two-d/ui/Skins.js.map +1 -0
  322. package/dist/two-d/ui/Slider.d.ts +91 -0
  323. package/dist/two-d/ui/Slider.d.ts.map +1 -0
  324. package/dist/two-d/ui/Slider.js +170 -0
  325. package/dist/two-d/ui/Slider.js.map +1 -0
  326. package/dist/two-d/ui/Spinner.d.ts +67 -0
  327. package/dist/two-d/ui/Spinner.d.ts.map +1 -0
  328. package/dist/two-d/ui/Spinner.js +137 -0
  329. package/dist/two-d/ui/Spinner.js.map +1 -0
  330. package/dist/two-d/ui/TabbedList.d.ts +118 -0
  331. package/dist/two-d/ui/TabbedList.d.ts.map +1 -0
  332. package/dist/two-d/ui/TabbedList.js +221 -0
  333. package/dist/two-d/ui/TabbedList.js.map +1 -0
  334. package/dist/two-d/ui/TextModel.d.ts +65 -0
  335. package/dist/two-d/ui/TextModel.d.ts.map +1 -0
  336. package/dist/two-d/ui/TextModel.js +126 -0
  337. package/dist/two-d/ui/TextModel.js.map +1 -0
  338. package/dist/two-d/ui/TreeView.d.ts +68 -0
  339. package/dist/two-d/ui/TreeView.d.ts.map +1 -0
  340. package/dist/two-d/ui/TreeView.js +133 -0
  341. package/dist/two-d/ui/TreeView.js.map +1 -0
  342. package/dist/two-d/ui/Window.d.ts +13 -0
  343. package/dist/two-d/ui/Window.d.ts.map +1 -1
  344. package/dist/two-d/ui/Window.js +23 -0
  345. package/dist/two-d/ui/Window.js.map +1 -1
  346. package/dist/two-d/ui/WindowStack.d.ts.map +1 -1
  347. package/dist/two-d/ui/WindowStack.js +2 -0
  348. package/dist/two-d/ui/WindowStack.js.map +1 -1
  349. package/dist/two-d/ui/index.d.ts +29 -3
  350. package/dist/two-d/ui/index.d.ts.map +1 -1
  351. package/dist/two-d/ui/index.js +15 -2
  352. package/dist/two-d/ui/index.js.map +1 -1
  353. package/dist/two-d/ui/markup.d.ts +181 -0
  354. package/dist/two-d/ui/markup.d.ts.map +1 -0
  355. package/dist/two-d/ui/markup.js +370 -0
  356. package/dist/two-d/ui/markup.js.map +1 -0
  357. package/dist/version.d.ts +1 -1
  358. package/dist/version.js +1 -1
  359. package/dist/world/SideTurns.d.ts +115 -0
  360. package/dist/world/SideTurns.d.ts.map +1 -0
  361. package/dist/world/SideTurns.js +138 -0
  362. package/dist/world/SideTurns.js.map +1 -0
  363. package/dist/world/index.d.ts +2 -0
  364. package/dist/world/index.d.ts.map +1 -1
  365. package/dist/world/index.js +1 -0
  366. package/dist/world/index.js.map +1 -1
  367. package/package.json +2 -1
@@ -0,0 +1,221 @@
1
+ import { Signal } from '../../core/Signal.js';
2
+ /**
3
+ * The model behind a tabbed, searchable, paged list - an inventory, journal, shop or codex -
4
+ * kept apart from how it is drawn, the same split `SelectionModel` already draws for plain
5
+ * lists. The caller supplies the tabs and the rows per tab (any type it likes, so no item
6
+ * taxonomy is assumed), plus optionally how a row labels, filters and disables itself; this
7
+ * owns the tab choice, the query, the selection, the page and the detail/close state.
8
+ *
9
+ * The page is derived from the selection rather than tracked beside it: a selection index
10
+ * into the filtered rows decides which page is visible, so the two can never disagree and
11
+ * `nextPage` is just a selection move of one page. `move` skips disabled rows.
12
+ *
13
+ * Rendering is the caller's: read `pageRows` and `selectedIndex` each frame, or listen to
14
+ * `onChange`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { TabbedList } from '@datamoc/mw_games/two-d/ui';
19
+ *
20
+ * const items = [
21
+ * { name: 'Sword', kind: 'weapon' },
22
+ * { name: 'Bread', kind: 'food' },
23
+ * { name: 'Shield', kind: 'armor' },
24
+ * ];
25
+ *
26
+ * const list = new TabbedList<(typeof items)[number]>({
27
+ * tabs: [{ id: 'all', label: 'All' }, { id: 'weapon', label: 'Weapons' }],
28
+ * rowsFor: (tab) => (tab === 'all' ? items : items.filter((item) => item.kind === tab)),
29
+ * label: (item) => item.name,
30
+ * pageSize: 2,
31
+ * });
32
+ *
33
+ * console.log(list.selected?.name); // 'Sword'
34
+ * list.setQuery('sh'); // filter down to Shield; selection and page reset
35
+ * console.log(list.rows.length, list.page); // 1 0
36
+ * list.openDetail(); // true, with the current row
37
+ * ```
38
+ */
39
+ export class TabbedList {
40
+ constructor(options) {
41
+ /** fires whenever the tab, query, selection, page or detail state changes */
42
+ this.onChange = new Signal();
43
+ this.currentTabIndex = 0;
44
+ this.currentQuery = '';
45
+ this.rows_ = [];
46
+ this.currentSelectedIndex = 0;
47
+ this.detail = false;
48
+ if (options.tabs.length === 0)
49
+ throw new Error('TabbedList needs at least one tab');
50
+ this.tabList = [...options.tabs];
51
+ this.rowsFor = options.rowsFor;
52
+ this.pageSize = Math.max(0, Math.floor(options.pageSize ?? 0));
53
+ this.labelOf = options.label ?? ((row) => String(row));
54
+ this.filterOf =
55
+ options.filter ??
56
+ ((row, query) => (query === '' ? true : this.labelOf(row).toLowerCase().includes(query.toLowerCase())));
57
+ this.disabledOf = options.disabled ?? (() => false);
58
+ this.recompute();
59
+ }
60
+ /** every tab, in display order, for a tab strip to draw */
61
+ get tabs() {
62
+ return this.tabList;
63
+ }
64
+ /** the currently open tab */
65
+ get tab() {
66
+ return this.tabList[this.currentTabIndex];
67
+ }
68
+ get query() {
69
+ return this.currentQuery;
70
+ }
71
+ /** the filtered rows of the current tab, across every page */
72
+ get rows() {
73
+ return this.rows_;
74
+ }
75
+ /** how many pages `rows` fills; always at least 1, so an empty list is one empty page */
76
+ get pageCount() {
77
+ if (this.pageSize <= 0)
78
+ return 1;
79
+ return Math.max(1, Math.ceil(this.rows_.length / this.pageSize));
80
+ }
81
+ /** the page the selection is on, derived from it rather than tracked next to it */
82
+ get page() {
83
+ if (this.pageSize <= 0)
84
+ return 0;
85
+ return Math.floor(this.currentSelectedIndex / this.pageSize);
86
+ }
87
+ /** the rows the current page shows */
88
+ get pageRows() {
89
+ if (this.pageSize <= 0)
90
+ return this.rows_;
91
+ const start = this.page * this.pageSize;
92
+ return this.rows_.slice(start, start + this.pageSize);
93
+ }
94
+ /** the selection's index into `rows` (not into `pageRows`) */
95
+ get selectedIndex() {
96
+ return this.currentSelectedIndex;
97
+ }
98
+ get selected() {
99
+ return this.rows_[this.currentSelectedIndex] ?? null;
100
+ }
101
+ /** whether the detail view has been opened for the current selection */
102
+ get detailOpen() {
103
+ return this.detail;
104
+ }
105
+ /** opens `id`'s tab; a disabled tab or an unknown id is ignored */
106
+ selectTab(id) {
107
+ const index = this.tabList.findIndex((tab) => tab.id === id);
108
+ if (index === -1 || this.tabList[index].disabled)
109
+ return;
110
+ if (index === this.currentTabIndex)
111
+ return;
112
+ this.currentTabIndex = index;
113
+ this.recompute();
114
+ this.emit();
115
+ }
116
+ /** moves to the next enabled tab, `delta` steps on (wrapping); skips disabled tabs */
117
+ nextTab(delta = 1) {
118
+ if (delta === 0)
119
+ return;
120
+ const step = delta > 0 ? 1 : -1;
121
+ let index = this.currentTabIndex;
122
+ for (let moved = 0; moved < Math.abs(delta); moved++) {
123
+ do {
124
+ index = (index + step + this.tabList.length) % this.tabList.length;
125
+ } while (this.tabList[index].disabled && index !== this.currentTabIndex);
126
+ }
127
+ if (index === this.currentTabIndex)
128
+ return;
129
+ this.currentTabIndex = index;
130
+ this.recompute();
131
+ this.emit();
132
+ }
133
+ /** replaces the query; the selection and page reset to the first row that survives it */
134
+ setQuery(query) {
135
+ if (query === this.currentQuery)
136
+ return;
137
+ this.currentQuery = query;
138
+ this.recompute();
139
+ this.emit();
140
+ }
141
+ /** moves the selection `delta` rows, skipping disabled ones and clamping at both ends */
142
+ move(delta) {
143
+ if (delta === 0 || this.rows_.length === 0)
144
+ return;
145
+ const step = delta > 0 ? 1 : -1;
146
+ let index = this.currentSelectedIndex;
147
+ for (let moved = 0; moved < Math.abs(delta); moved++) {
148
+ let candidate = index;
149
+ do {
150
+ candidate += step;
151
+ } while (candidate >= 0 && candidate < this.rows_.length && this.disabledOf(this.rows_[candidate]));
152
+ if (candidate < 0 || candidate >= this.rows_.length)
153
+ break;
154
+ index = candidate;
155
+ }
156
+ if (index === this.currentSelectedIndex)
157
+ return;
158
+ this.currentSelectedIndex = index;
159
+ this.emit();
160
+ }
161
+ /** jumps to a page by number, clamped; the selection moves to its first selectable row */
162
+ setPage(page) {
163
+ const target = Math.max(0, Math.min(this.pageCount - 1, Math.floor(page)));
164
+ if (this.pageSize <= 0)
165
+ return;
166
+ const first = this.firstSelectableOnPage(target);
167
+ if (first === this.currentSelectedIndex)
168
+ return;
169
+ this.currentSelectedIndex = first;
170
+ this.emit();
171
+ }
172
+ /** moves `delta` pages; the selection lands on the target page's first selectable row */
173
+ nextPage(delta) {
174
+ this.setPage(this.page + delta);
175
+ }
176
+ /** opens the detail view for the current selection; false when there is nothing to open */
177
+ openDetail() {
178
+ if (this.selected === null || this.disabledOf(this.selected))
179
+ return false;
180
+ if (!this.detail) {
181
+ this.detail = true;
182
+ this.emit();
183
+ }
184
+ return true;
185
+ }
186
+ closeDetail() {
187
+ if (!this.detail)
188
+ return;
189
+ this.detail = false;
190
+ this.emit();
191
+ }
192
+ /** the first row on `page` that is not disabled, or its first row when all are */
193
+ firstSelectableOnPage(page) {
194
+ const start = page * this.pageSize;
195
+ const end = Math.min(this.rows_.length, start + this.pageSize);
196
+ for (let i = start; i < end; i++) {
197
+ if (!this.disabledOf(this.rows_[i]))
198
+ return i;
199
+ }
200
+ return Math.min(start, Math.max(0, this.rows_.length - 1));
201
+ }
202
+ firstSelectable(from) {
203
+ for (let i = from; i < this.rows_.length; i++) {
204
+ if (!this.disabledOf(this.rows_[i]))
205
+ return i;
206
+ }
207
+ return 0;
208
+ }
209
+ /** rebuilds the visible rows and parks the selection on the first selectable one */
210
+ recompute() {
211
+ const all = this.rowsFor(this.tab.id);
212
+ const filtered = all.filter((row) => this.filterOf(row, this.currentQuery));
213
+ this.rows_ = filtered;
214
+ this.currentSelectedIndex = filtered.length === 0 ? 0 : this.firstSelectable(0);
215
+ this.detail = false;
216
+ }
217
+ emit() {
218
+ this.onChange.dispatch();
219
+ }
220
+ }
221
+ //# sourceMappingURL=TabbedList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabbedList.js","sourceRoot":"","sources":["../../../src/two-d/ui/TabbedList.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ export interface TextModelOptions {
2
+ value?: string;
3
+ /** the most characters the field will hold; unlimited when omitted */
4
+ maxLength?: number;
5
+ /** a password-style field: `maskedValue` replaces every character with a dot */
6
+ mask?: boolean;
7
+ /** the character `maskedValue` repeats; a bullet when omitted */
8
+ maskCharacter?: string;
9
+ }
10
+ /**
11
+ * The editing state behind a text field, with no renderer attached - the counterpart to
12
+ * `core.Input`'s `onText`, which supplies the characters: the DOM gives a key, this decides
13
+ * where it goes, and a game draws `value` (or `maskedValue`) with the caret it reports.
14
+ *
15
+ * The caret is an index into `value`; a selection is the caret plus an anchor, so every edit
16
+ * replaces the selection when there is one and inserts at the caret when there is not, which is
17
+ * the behaviour a plain `<input>` has and a title screen's name entry expects.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { TextModel } from '@datamoc/mw_games/two-d/ui';
22
+ *
23
+ * const name = new TextModel({ maxLength: 8, value: 'Hero' });
24
+ * name.selectAll();
25
+ * name.insert('Ash');
26
+ * console.log(name.value); // 'Ash'
27
+ * ```
28
+ */
29
+ export declare class TextModel {
30
+ private text;
31
+ private caretIndex;
32
+ private anchor;
33
+ private readonly maxLength?;
34
+ private readonly mask;
35
+ private readonly maskCharacter;
36
+ constructor(options?: TextModelOptions);
37
+ get value(): string;
38
+ /** what to draw for a password field: every character replaced by the mask */
39
+ get maskedValue(): string;
40
+ get length(): number;
41
+ get caret(): number;
42
+ get selectionStart(): number;
43
+ get selectionEnd(): number;
44
+ get hasSelection(): boolean;
45
+ get selectedText(): string;
46
+ /** replaces everything; the caret goes to the end and a new string is never longer than `maxLength` */
47
+ setValue(value: string): void;
48
+ setCaret(index: number, extend?: boolean): void;
49
+ /** inserts at the caret, replacing the selection, as a keystroke from `onText` does */
50
+ insert(text: string): void;
51
+ /** the backspace key: removes the selection, or the character before the caret */
52
+ backspace(): void;
53
+ /** the delete key: removes the selection, or the character after the caret */
54
+ deleteForward(): void;
55
+ /** arrow keys: moves the caret, extending the selection when the shift key is held */
56
+ moveCaret(delta: number, extend?: boolean): void;
57
+ moveToStart(extend?: boolean): void;
58
+ moveToEnd(extend?: boolean): void;
59
+ selectAll(): void;
60
+ clearSelection(): void;
61
+ /** replaces the selection (or inserts at the caret) and leaves the caret after the new text */
62
+ replaceSelection(text: string): void;
63
+ private limit;
64
+ }
65
+ //# sourceMappingURL=TextModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextModel.d.ts","sourceRoot":"","sources":["../../../src/two-d/ui/TextModel.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,SAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAU;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAAY,OAAO,GAAE,gBAAqB,EAOzC;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,8EAA8E;IAC9E,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,uGAAuG;IACvG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI5B;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,IAAI,CAG5C;IAED,uFAAuF;IACvF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAGzB;IAED,kFAAkF;IAClF,SAAS,IAAI,IAAI,CAQhB;IAED,8EAA8E;IAC9E,aAAa,IAAI,IAAI,CAQpB;IAED,sFAAsF;IACtF,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,IAAI,CAE7C;IAED,WAAW,CAAC,MAAM,UAAQ,GAAG,IAAI,CAEhC;IAED,SAAS,CAAC,MAAM,UAAQ,GAAG,IAAI,CAE9B;IAED,SAAS,IAAI,IAAI,CAGhB;IAED,cAAc,IAAI,IAAI,CAErB;IAED,+FAA+F;IAC/F,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAWnC;IAED,OAAO,CAAC,KAAK;CAGb"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The editing state behind a text field, with no renderer attached - the counterpart to
3
+ * `core.Input`'s `onText`, which supplies the characters: the DOM gives a key, this decides
4
+ * where it goes, and a game draws `value` (or `maskedValue`) with the caret it reports.
5
+ *
6
+ * The caret is an index into `value`; a selection is the caret plus an anchor, so every edit
7
+ * replaces the selection when there is one and inserts at the caret when there is not, which is
8
+ * the behaviour a plain `<input>` has and a title screen's name entry expects.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { TextModel } from '@datamoc/mw_games/two-d/ui';
13
+ *
14
+ * const name = new TextModel({ maxLength: 8, value: 'Hero' });
15
+ * name.selectAll();
16
+ * name.insert('Ash');
17
+ * console.log(name.value); // 'Ash'
18
+ * ```
19
+ */
20
+ export class TextModel {
21
+ constructor(options = {}) {
22
+ this.text = options.value ?? '';
23
+ this.maxLength = options.maxLength;
24
+ this.mask = options.mask ?? false;
25
+ this.maskCharacter = options.maskCharacter ?? '\u2022';
26
+ this.caretIndex = this.text.length;
27
+ this.anchor = this.caretIndex;
28
+ }
29
+ get value() {
30
+ return this.text;
31
+ }
32
+ /** what to draw for a password field: every character replaced by the mask */
33
+ get maskedValue() {
34
+ return this.mask ? this.maskCharacter.repeat(this.text.length) : this.text;
35
+ }
36
+ get length() {
37
+ return this.text.length;
38
+ }
39
+ get caret() {
40
+ return this.caretIndex;
41
+ }
42
+ get selectionStart() {
43
+ return Math.min(this.caretIndex, this.anchor);
44
+ }
45
+ get selectionEnd() {
46
+ return Math.max(this.caretIndex, this.anchor);
47
+ }
48
+ get hasSelection() {
49
+ return this.caretIndex !== this.anchor;
50
+ }
51
+ get selectedText() {
52
+ return this.text.slice(this.selectionStart, this.selectionEnd);
53
+ }
54
+ /** replaces everything; the caret goes to the end and a new string is never longer than `maxLength` */
55
+ setValue(value) {
56
+ this.text = this.limit(value);
57
+ this.caretIndex = this.text.length;
58
+ this.anchor = this.caretIndex;
59
+ }
60
+ setCaret(index, extend = false) {
61
+ this.caretIndex = Math.max(0, Math.min(this.text.length, index));
62
+ if (!extend)
63
+ this.anchor = this.caretIndex;
64
+ }
65
+ /** inserts at the caret, replacing the selection, as a keystroke from `onText` does */
66
+ insert(text) {
67
+ if (text.length === 0)
68
+ return;
69
+ this.replaceSelection(text);
70
+ }
71
+ /** the backspace key: removes the selection, or the character before the caret */
72
+ backspace() {
73
+ if (this.hasSelection) {
74
+ this.replaceSelection('');
75
+ return;
76
+ }
77
+ if (this.caretIndex === 0)
78
+ return;
79
+ this.text = this.text.slice(0, this.caretIndex - 1) + this.text.slice(this.caretIndex);
80
+ this.setCaret(this.caretIndex - 1);
81
+ }
82
+ /** the delete key: removes the selection, or the character after the caret */
83
+ deleteForward() {
84
+ if (this.hasSelection) {
85
+ this.replaceSelection('');
86
+ return;
87
+ }
88
+ if (this.caretIndex >= this.text.length)
89
+ return;
90
+ this.text = this.text.slice(0, this.caretIndex) + this.text.slice(this.caretIndex + 1);
91
+ this.setCaret(this.caretIndex);
92
+ }
93
+ /** arrow keys: moves the caret, extending the selection when the shift key is held */
94
+ moveCaret(delta, extend = false) {
95
+ this.setCaret(this.caretIndex + delta, extend);
96
+ }
97
+ moveToStart(extend = false) {
98
+ this.setCaret(0, extend);
99
+ }
100
+ moveToEnd(extend = false) {
101
+ this.setCaret(this.text.length, extend);
102
+ }
103
+ selectAll() {
104
+ this.anchor = 0;
105
+ this.caretIndex = this.text.length;
106
+ }
107
+ clearSelection() {
108
+ this.anchor = this.caretIndex;
109
+ }
110
+ /** replaces the selection (or inserts at the caret) and leaves the caret after the new text */
111
+ replaceSelection(text) {
112
+ const start = this.selectionStart;
113
+ const end = this.selectionEnd;
114
+ const base = this.text.slice(0, start) + this.text.slice(end);
115
+ //a paste into a field with little room left stops at the cap rather than being rejected whole
116
+ const inserted = this.maxLength !== undefined && base.length + text.length > this.maxLength
117
+ ? text.slice(0, Math.max(0, this.maxLength - base.length))
118
+ : text;
119
+ this.text = this.text.slice(0, start) + inserted + this.text.slice(end);
120
+ this.setCaret(start + inserted.length);
121
+ }
122
+ limit(value) {
123
+ return this.maxLength !== undefined && value.length > this.maxLength ? value.slice(0, this.maxLength) : value;
124
+ }
125
+ }
126
+ //# sourceMappingURL=TextModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextModel.js","sourceRoot":"","sources":["../../../src/two-d/ui/TextModel.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,SAAS;IAQrB,YAAY,OAAO,GAAqB,EAAE;QACzC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC;QAClC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,QAAQ,CAAC;QACvD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,IAAI,CAAC;IAClB,CAAC;IAED,8EAA8E;IAC9E,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5E,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IACzB,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED,IAAI,cAAc;QACjB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,YAAY;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,YAAY;QACf,OAAO,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC;IACxC,CAAC;IAED,IAAI,YAAY;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAChE,CAAC;IAED,uGAAuG;IACvG,QAAQ,CAAC,KAAa;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC/B,CAAC;IAED,QAAQ,CAAC,KAAa,EAAE,MAAM,GAAG,KAAK;QACrC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC5C,CAAC;IAED,uFAAuF;IACvF,MAAM,CAAC,IAAY;QAClB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,kFAAkF;IAClF,SAAS;QACR,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC;YAAE,OAAO;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACpC,CAAC;IAED,8EAA8E;IAC9E,aAAa;QACZ,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAChD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QACvF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAED,sFAAsF;IACtF,SAAS,CAAC,KAAa,EAAE,MAAM,GAAG,KAAK;QACtC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,WAAW,CAAC,MAAM,GAAG,KAAK;QACzB,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,SAAS,CAAC,MAAM,GAAG,KAAK;QACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IAED,SAAS;QACR,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IACpC,CAAC;IAED,cAAc;QACb,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC/B,CAAC;IAED,+FAA+F;IAC/F,gBAAgB,CAAC,IAAY;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9D,8FAA8F;QAC9F,MAAM,QAAQ,GACb,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS;YACzE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1D,CAAC,CAAC,IAAI,CAAC;QACT,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxE,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAEO,KAAK,CAAC,KAAa;QAC1B,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/G,CAAC;CACD"}
@@ -0,0 +1,68 @@
1
+ import { Signal } from '../../core/Signal.ts';
2
+ export interface TreeNode<T = unknown> {
3
+ /** stable across rebuilds: the id expansion state is kept by */
4
+ id: string;
5
+ label: string;
6
+ children?: readonly TreeNode<T>[];
7
+ disabled?: boolean;
8
+ data?: T;
9
+ }
10
+ /** A tree node as the flattened view holds it: its depth and whether its children are showing. */
11
+ export interface TreeRow<T> {
12
+ node: TreeNode<T>;
13
+ depth: number;
14
+ expanded: boolean;
15
+ hasChildren: boolean;
16
+ }
17
+ export interface TreeViewOptions<T> {
18
+ roots: readonly TreeNode<T>[];
19
+ /** ids expanded from the start; everything else starts collapsed */
20
+ expanded?: readonly string[];
21
+ /** rows the highlight skips, the way `ListView` skips a disabled item */
22
+ disabled?: (node: TreeNode<T>) => boolean;
23
+ }
24
+ /**
25
+ * A collapsible tree, flattened to the rows a game draws - a skill tree, a nested options menu,
26
+ * a directory of scenarios grouped by campaign.
27
+ *
28
+ * Renderer-free, like `TabbedList`. The visible rows are derived from the roots and the set of
29
+ * expanded ids every time they are asked for, rather than kept in a parallel list that could go
30
+ * stale. Two rules are worth naming: collapsing a branch that contains the highlight moves the
31
+ * highlight to the branch itself, and `move` only ever walks the rows actually on screen, so a
32
+ * collapsed child is unreachable without expanding it first.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { TreeView } from '@datamoc/mw_games/two-d/ui';
37
+ *
38
+ * const tree = new TreeView({
39
+ * roots: [{ id: 'a', label: 'A', children: [{ id: 'a1', label: 'A1' }] }],
40
+ * });
41
+ * tree.expand('a');
42
+ * console.log(tree.rows.map((row) => row.node.id)); // ['a', 'a1']
43
+ * ```
44
+ */
45
+ export declare class TreeView<T = unknown> {
46
+ readonly onChange: Signal<void>;
47
+ private roots_;
48
+ private expanded;
49
+ private current;
50
+ private readonly disabledOf;
51
+ constructor(options: TreeViewOptions<T>);
52
+ get roots(): readonly TreeNode<T>[];
53
+ /** the rows on screen, in draw order: a collapsed node's children are absent */
54
+ get rows(): readonly TreeRow<T>[];
55
+ get selectedIndex(): number;
56
+ get selected(): TreeNode<T> | null;
57
+ isExpanded(id: string): boolean;
58
+ expand(id: string): void;
59
+ collapse(id: string): void;
60
+ toggle(id: string): void;
61
+ expandAll(): void;
62
+ collapseAll(): void;
63
+ select(index: number): void;
64
+ /** moves the highlight a visible row at a time, skipping disabled rows and stopping at the ends */
65
+ move(delta: number): void;
66
+ private firstEnabled;
67
+ }
68
+ //# sourceMappingURL=TreeView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreeView.d.ts","sourceRoot":"","sources":["../../../src/two-d/ui/TreeView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACpC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,CAAC;CACT;AAED,kGAAkG;AAClG,MAAM,WAAW,OAAO,CAAC,CAAC;IACzB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,KAAK,EAAE,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,QAAQ,CAAC,CAAC,GAAG,OAAO;IAChC,QAAQ,CAAC,QAAQ,eAAsB;IAEvC,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;IAE5D,YAAY,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAKtC;IAED,IAAI,KAAK,IAAI,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,CAElC;IAED,gFAAgF;IAChF,IAAI,IAAI,IAAI,SAAS,OAAO,CAAC,CAAC,CAAC,EAAE,CAYhC;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,IAAI,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAEjC;IAED,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE9B;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAIvB;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAWzB;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAGvB;IAED,SAAS,IAAI,IAAI,CAWhB;IAED,WAAW,IAAI,IAAI,CAIlB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAK1B;IAED,mGAAmG;IACnG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAaxB;IAED,OAAO,CAAC,YAAY;CAIpB"}
@@ -0,0 +1,133 @@
1
+ import { Signal } from '../../core/Signal.js';
2
+ /**
3
+ * A collapsible tree, flattened to the rows a game draws - a skill tree, a nested options menu,
4
+ * a directory of scenarios grouped by campaign.
5
+ *
6
+ * Renderer-free, like `TabbedList`. The visible rows are derived from the roots and the set of
7
+ * expanded ids every time they are asked for, rather than kept in a parallel list that could go
8
+ * stale. Two rules are worth naming: collapsing a branch that contains the highlight moves the
9
+ * highlight to the branch itself, and `move` only ever walks the rows actually on screen, so a
10
+ * collapsed child is unreachable without expanding it first.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { TreeView } from '@datamoc/mw_games/two-d/ui';
15
+ *
16
+ * const tree = new TreeView({
17
+ * roots: [{ id: 'a', label: 'A', children: [{ id: 'a1', label: 'A1' }] }],
18
+ * });
19
+ * tree.expand('a');
20
+ * console.log(tree.rows.map((row) => row.node.id)); // ['a', 'a1']
21
+ * ```
22
+ */
23
+ export class TreeView {
24
+ constructor(options) {
25
+ this.onChange = new Signal();
26
+ this.expanded = new Set();
27
+ this.current = 0;
28
+ this.roots_ = options.roots;
29
+ for (const id of options.expanded ?? [])
30
+ this.expanded.add(id);
31
+ this.disabledOf = options.disabled ?? (() => false);
32
+ this.current = this.firstEnabled();
33
+ }
34
+ get roots() {
35
+ return this.roots_;
36
+ }
37
+ /** the rows on screen, in draw order: a collapsed node's children are absent */
38
+ get rows() {
39
+ const rows = [];
40
+ const walk = (nodes, depth) => {
41
+ for (const node of nodes) {
42
+ const hasChildren = (node.children?.length ?? 0) > 0;
43
+ const isExpanded = hasChildren && this.expanded.has(node.id);
44
+ rows.push({ node, depth, expanded: isExpanded, hasChildren });
45
+ if (isExpanded)
46
+ walk(node.children, depth + 1);
47
+ }
48
+ };
49
+ walk(this.roots_, 0);
50
+ return rows;
51
+ }
52
+ get selectedIndex() {
53
+ return this.current;
54
+ }
55
+ get selected() {
56
+ return this.rows[this.current]?.node ?? null;
57
+ }
58
+ isExpanded(id) {
59
+ return this.expanded.has(id);
60
+ }
61
+ expand(id) {
62
+ if (this.expanded.has(id))
63
+ return;
64
+ this.expanded.add(id);
65
+ this.onChange.dispatch();
66
+ }
67
+ collapse(id) {
68
+ if (!this.expanded.has(id))
69
+ return;
70
+ //read the selection before closing, or a child index can already point past the shorter list
71
+ const selectedId = this.rows[this.current]?.node.id;
72
+ this.expanded.delete(id);
73
+ //if the highlight was inside the branch that just closed, land it on the branch itself
74
+ if (selectedId !== undefined && !this.rows.some((row) => row.node.id === selectedId)) {
75
+ const index = this.rows.findIndex((row) => row.node.id === id);
76
+ if (index >= 0)
77
+ this.current = index;
78
+ }
79
+ this.onChange.dispatch();
80
+ }
81
+ toggle(id) {
82
+ if (this.expanded.has(id))
83
+ this.collapse(id);
84
+ else
85
+ this.expand(id);
86
+ }
87
+ expandAll() {
88
+ const walk = (nodes) => {
89
+ for (const node of nodes) {
90
+ if (node.children?.length) {
91
+ this.expanded.add(node.id);
92
+ walk(node.children);
93
+ }
94
+ }
95
+ };
96
+ walk(this.roots_);
97
+ this.onChange.dispatch();
98
+ }
99
+ collapseAll() {
100
+ this.expanded.clear();
101
+ this.select(0);
102
+ this.onChange.dispatch();
103
+ }
104
+ select(index) {
105
+ const row = this.rows[index];
106
+ if (!row || this.disabledOf(row.node))
107
+ return;
108
+ this.current = index;
109
+ this.onChange.dispatch();
110
+ }
111
+ /** moves the highlight a visible row at a time, skipping disabled rows and stopping at the ends */
112
+ move(delta) {
113
+ const count = this.rows.length;
114
+ if (count === 0)
115
+ return;
116
+ let index = this.current;
117
+ for (let tried = 0; tried < count; tried++) {
118
+ index += delta;
119
+ if (index < 0 || index >= count)
120
+ return;
121
+ if (!this.disabledOf(this.rows[index].node)) {
122
+ this.current = index;
123
+ this.onChange.dispatch();
124
+ return;
125
+ }
126
+ }
127
+ }
128
+ firstEnabled() {
129
+ const index = this.rows.findIndex((row) => !this.disabledOf(row.node));
130
+ return index === -1 ? 0 : index;
131
+ }
132
+ }
133
+ //# sourceMappingURL=TreeView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreeView.js","sourceRoot":"","sources":["../../../src/two-d/ui/TreeView.ts"],"names":[],"mappings":"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"}