@fungi.computer/caps 0.0.0-stage → 0.0.1

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 (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1125 -2
  3. package/dist/blocks/app-bar.d.ts +43 -0
  4. package/dist/blocks/app-bar.js +244 -0
  5. package/dist/blocks/app-status-bar.d.ts +93 -0
  6. package/dist/blocks/app-status-bar.js +42 -0
  7. package/dist/blocks/async-state.d.ts +21 -0
  8. package/dist/blocks/async-state.js +8 -0
  9. package/dist/blocks/card-grid.d.ts +11 -0
  10. package/dist/blocks/card-grid.js +21 -0
  11. package/dist/blocks/command-menu.d.ts +26 -0
  12. package/dist/blocks/command-menu.js +70 -0
  13. package/dist/blocks/composer.d.ts +23 -0
  14. package/dist/blocks/composer.js +13 -0
  15. package/dist/blocks/empty-state.d.ts +59 -0
  16. package/dist/blocks/empty-state.js +35 -0
  17. package/dist/blocks/faq-chat.d.ts +62 -0
  18. package/dist/blocks/faq-chat.js +77 -0
  19. package/dist/blocks/game-share-card.d.ts +17 -0
  20. package/dist/blocks/game-share-card.js +9 -0
  21. package/dist/blocks/launcher-grid.d.ts +50 -0
  22. package/dist/blocks/launcher-grid.js +11 -0
  23. package/dist/blocks/listing-detail.d.ts +23 -0
  24. package/dist/blocks/listing-detail.js +12 -0
  25. package/dist/blocks/loading-state.d.ts +5 -0
  26. package/dist/blocks/loading-state.js +8 -0
  27. package/dist/blocks/mosaic.d.ts +29 -0
  28. package/dist/blocks/mosaic.js +19 -0
  29. package/dist/blocks/permission-grid.d.ts +45 -0
  30. package/dist/blocks/permission-grid.js +14 -0
  31. package/dist/blocks/pricing-card.d.ts +53 -0
  32. package/dist/blocks/pricing-card.js +12 -0
  33. package/dist/blocks/release-tag.d.ts +20 -0
  34. package/dist/blocks/release-tag.js +10 -0
  35. package/dist/blocks/resource-select.d.ts +22 -0
  36. package/dist/blocks/resource-select.js +16 -0
  37. package/dist/blocks/result-state.d.ts +15 -0
  38. package/dist/blocks/result-state.js +8 -0
  39. package/dist/blocks/section-card.d.ts +31 -0
  40. package/dist/blocks/section-card.js +7 -0
  41. package/dist/blocks/section-heading.d.ts +13 -0
  42. package/dist/blocks/section-heading.js +11 -0
  43. package/dist/blocks/spec-sheet.d.ts +125 -0
  44. package/dist/blocks/spec-sheet.js +47 -0
  45. package/dist/blocks/status-tray.d.ts +83 -0
  46. package/dist/blocks/status-tray.js +79 -0
  47. package/dist/blocks/upload-queue.d.ts +48 -0
  48. package/dist/blocks/upload-queue.js +24 -0
  49. package/dist/components/action-row.d.ts +16 -0
  50. package/dist/components/action-row.js +11 -0
  51. package/dist/components/activity-line.d.ts +33 -0
  52. package/dist/components/activity-line.js +69 -0
  53. package/dist/components/alert.d.ts +17 -0
  54. package/dist/components/alert.js +17 -0
  55. package/dist/components/badge.d.ts +19 -0
  56. package/dist/components/badge.js +24 -0
  57. package/dist/components/button.d.ts +55 -0
  58. package/dist/components/button.js +230 -0
  59. package/dist/components/card.d.ts +61 -0
  60. package/dist/components/card.js +48 -0
  61. package/dist/components/chat-bubble.d.ts +34 -0
  62. package/dist/components/chat-bubble.js +10 -0
  63. package/dist/components/checkbox-bead.d.ts +6 -0
  64. package/dist/components/checkbox-bead.js +67 -0
  65. package/dist/components/checkbox.d.ts +21 -0
  66. package/dist/components/checkbox.js +46 -0
  67. package/dist/components/code-input.d.ts +35 -0
  68. package/dist/components/code-input.js +126 -0
  69. package/dist/components/combobox.d.ts +86 -0
  70. package/dist/components/combobox.js +218 -0
  71. package/dist/components/confirm-action.d.ts +23 -0
  72. package/dist/components/confirm-action.js +64 -0
  73. package/dist/components/copy-field.d.ts +53 -0
  74. package/dist/components/copy-field.js +49 -0
  75. package/dist/components/count-badge.d.ts +15 -0
  76. package/dist/components/count-badge.js +9 -0
  77. package/dist/components/cover-pattern.d.ts +19 -0
  78. package/dist/components/cover-pattern.js +74 -0
  79. package/dist/components/data-list.d.ts +25 -0
  80. package/dist/components/data-list.js +16 -0
  81. package/dist/components/dialog.d.ts +39 -0
  82. package/dist/components/dialog.js +73 -0
  83. package/dist/components/diff-view.d.ts +14 -0
  84. package/dist/components/diff-view.js +13 -0
  85. package/dist/components/disclosure.d.ts +29 -0
  86. package/dist/components/disclosure.js +19 -0
  87. package/dist/components/dropdown-menu.d.ts +49 -0
  88. package/dist/components/dropdown-menu.js +98 -0
  89. package/dist/components/field-guide.d.ts +41 -0
  90. package/dist/components/field-guide.js +18 -0
  91. package/dist/components/field.d.ts +164 -0
  92. package/dist/components/field.js +228 -0
  93. package/dist/components/game-feedback.d.ts +84 -0
  94. package/dist/components/game-feedback.js +48 -0
  95. package/dist/components/game-hud.d.ts +126 -0
  96. package/dist/components/game-hud.js +136 -0
  97. package/dist/components/generation-frame.d.ts +36 -0
  98. package/dist/components/generation-frame.js +97 -0
  99. package/dist/components/gooey.d.ts +32 -0
  100. package/dist/components/gooey.js +25 -0
  101. package/dist/components/hold-button.d.ts +26 -0
  102. package/dist/components/hold-button.js +115 -0
  103. package/dist/components/icon-button.d.ts +24 -0
  104. package/dist/components/icon-button.js +18 -0
  105. package/dist/components/icon-checkbox.d.ts +20 -0
  106. package/dist/components/icon-checkbox.js +9 -0
  107. package/dist/components/icon-choice-mark.d.ts +6 -0
  108. package/dist/components/icon-choice-mark.js +5 -0
  109. package/dist/components/icon-lamp.d.ts +42 -0
  110. package/dist/components/icon-lamp.js +15 -0
  111. package/dist/components/icon-radio.d.ts +16 -0
  112. package/dist/components/icon-radio.js +9 -0
  113. package/dist/components/inline-code.d.ts +3 -0
  114. package/dist/components/inline-code.js +7 -0
  115. package/dist/components/input-group.d.ts +9 -0
  116. package/dist/components/input-group.js +15 -0
  117. package/dist/components/input.d.ts +15 -0
  118. package/dist/components/input.js +15 -0
  119. package/dist/components/item-grid.d.ts +110 -0
  120. package/dist/components/item-grid.js +136 -0
  121. package/dist/components/keycap.d.ts +80 -0
  122. package/dist/components/keycap.js +18 -0
  123. package/dist/components/label.d.ts +13 -0
  124. package/dist/components/label.js +7 -0
  125. package/dist/components/listbox.d.ts +50 -0
  126. package/dist/components/listbox.js +134 -0
  127. package/dist/components/loader-blobs.d.ts +5 -0
  128. package/dist/components/loader-blobs.js +128 -0
  129. package/dist/components/loader.d.ts +21 -0
  130. package/dist/components/loader.js +25 -0
  131. package/dist/components/markdown.d.ts +23 -0
  132. package/dist/components/markdown.js +70 -0
  133. package/dist/components/minimap-labels.d.ts +39 -0
  134. package/dist/components/minimap-labels.js +113 -0
  135. package/dist/components/monogram.d.ts +32 -0
  136. package/dist/components/monogram.js +24 -0
  137. package/dist/components/motion-visibility.d.ts +2 -0
  138. package/dist/components/motion-visibility.js +25 -0
  139. package/dist/components/navigation-rail.d.ts +11 -0
  140. package/dist/components/navigation-rail.js +55 -0
  141. package/dist/components/overlay-root.d.ts +16 -0
  142. package/dist/components/overlay-root.js +24 -0
  143. package/dist/components/pixel-number.d.ts +50 -0
  144. package/dist/components/pixel-number.js +113 -0
  145. package/dist/components/popover.d.ts +31 -0
  146. package/dist/components/popover.js +24 -0
  147. package/dist/components/presence.d.ts +59 -0
  148. package/dist/components/presence.js +232 -0
  149. package/dist/components/price-tag.d.ts +11 -0
  150. package/dist/components/price-tag.js +6 -0
  151. package/dist/components/progress.d.ts +25 -0
  152. package/dist/components/progress.js +24 -0
  153. package/dist/components/prose.d.ts +14 -0
  154. package/dist/components/prose.js +10 -0
  155. package/dist/components/quota-meter.d.ts +24 -0
  156. package/dist/components/quota-meter.js +11 -0
  157. package/dist/components/radio-bead.d.ts +4 -0
  158. package/dist/components/radio-bead.js +53 -0
  159. package/dist/components/radio-group.d.ts +54 -0
  160. package/dist/components/radio-group.js +64 -0
  161. package/dist/components/resize-handle.d.ts +23 -0
  162. package/dist/components/resize-handle.js +31 -0
  163. package/dist/components/rough-drawing.d.ts +42 -0
  164. package/dist/components/rough-drawing.js +231 -0
  165. package/dist/components/rough.d.ts +68 -0
  166. package/dist/components/rough.js +33 -0
  167. package/dist/components/scroll-area.d.ts +12 -0
  168. package/dist/components/scroll-area.js +96 -0
  169. package/dist/components/scroll-motion.d.ts +12 -0
  170. package/dist/components/scroll-motion.js +31 -0
  171. package/dist/components/scroll-to-bottom-blob-state.d.ts +40 -0
  172. package/dist/components/scroll-to-bottom-blob-state.js +109 -0
  173. package/dist/components/scroll-to-bottom-blob.d.ts +16 -0
  174. package/dist/components/scroll-to-bottom-blob.js +174 -0
  175. package/dist/components/select.d.ts +9 -0
  176. package/dist/components/select.js +12 -0
  177. package/dist/components/selectable.d.ts +13 -0
  178. package/dist/components/selectable.js +45 -0
  179. package/dist/components/send-action.d.ts +33 -0
  180. package/dist/components/send-action.js +96 -0
  181. package/dist/components/separator.d.ts +11 -0
  182. package/dist/components/separator.js +7 -0
  183. package/dist/components/skeleton.d.ts +5 -0
  184. package/dist/components/skeleton.js +7 -0
  185. package/dist/components/slider.d.ts +18 -0
  186. package/dist/components/slider.js +31 -0
  187. package/dist/components/stat.d.ts +17 -0
  188. package/dist/components/stat.js +7 -0
  189. package/dist/components/status-bead.d.ts +9 -0
  190. package/dist/components/status-bead.js +107 -0
  191. package/dist/components/status-mark.d.ts +12 -0
  192. package/dist/components/status-mark.js +177 -0
  193. package/dist/components/status.d.ts +38 -0
  194. package/dist/components/status.js +28 -0
  195. package/dist/components/store-card.d.ts +21 -0
  196. package/dist/components/store-card.js +10 -0
  197. package/dist/components/surface.d.ts +89 -0
  198. package/dist/components/surface.js +31 -0
  199. package/dist/components/switch-bead.d.ts +4 -0
  200. package/dist/components/switch-bead.js +62 -0
  201. package/dist/components/switch.d.ts +11 -0
  202. package/dist/components/switch.js +36 -0
  203. package/dist/components/tabs.d.ts +55 -0
  204. package/dist/components/tabs.js +81 -0
  205. package/dist/components/technonomicon.d.ts +192 -0
  206. package/dist/components/technonomicon.js +121 -0
  207. package/dist/components/textarea.d.ts +9 -0
  208. package/dist/components/textarea.js +11 -0
  209. package/dist/components/toaster.d.ts +4 -0
  210. package/dist/components/toaster.js +13 -0
  211. package/dist/components/tool-call-chip.d.ts +22 -0
  212. package/dist/components/tool-call-chip.js +100 -0
  213. package/dist/components/tooltip.d.ts +41 -0
  214. package/dist/components/tooltip.js +169 -0
  215. package/dist/components/trading-card.d.ts +60 -0
  216. package/dist/components/trading-card.js +13 -0
  217. package/dist/components/undo-action.d.ts +52 -0
  218. package/dist/components/undo-action.js +100 -0
  219. package/dist/components/undo-toast.d.ts +21 -0
  220. package/dist/components/undo-toast.js +58 -0
  221. package/dist/components/visually-hidden.d.ts +9 -0
  222. package/dist/components/visually-hidden.js +11 -0
  223. package/dist/components/window.d.ts +65 -0
  224. package/dist/components/window.js +31 -0
  225. package/dist/components/wizard.d.ts +17 -0
  226. package/dist/components/wizard.js +19 -0
  227. package/dist/enhance/cigarette-card.d.ts +8 -0
  228. package/dist/enhance/cigarette-card.js +33 -0
  229. package/dist/enhance/sketch.d.ts +16 -0
  230. package/dist/enhance/sketch.js +130 -0
  231. package/dist/form-error.d.ts +7 -0
  232. package/dist/form-error.js +14 -0
  233. package/dist/form-fields.d.ts +15 -0
  234. package/dist/form-fields.js +250 -0
  235. package/dist/form-schema.d.ts +166 -0
  236. package/dist/form-schema.js +398 -0
  237. package/dist/form.d.ts +30 -0
  238. package/dist/form.js +20 -0
  239. package/dist/lib/cn.d.ts +8 -0
  240. package/dist/lib/cn.js +12 -0
  241. package/dist/lib/format-money.d.ts +2 -0
  242. package/dist/lib/format-money.js +22 -0
  243. package/dist/lib/glide.d.ts +12 -0
  244. package/dist/lib/glide.js +143 -0
  245. package/dist/lib/ime.d.ts +3 -0
  246. package/dist/lib/ime.js +8 -0
  247. package/dist/lib/motion-spring.d.ts +27 -0
  248. package/dist/lib/motion-spring.js +36 -0
  249. package/dist/lib/motion.d.ts +51 -0
  250. package/dist/lib/motion.js +161 -0
  251. package/dist/lib/reorder-motion.d.ts +13 -0
  252. package/dist/lib/reorder-motion.js +201 -0
  253. package/dist/lib/seed-hash.d.ts +6 -0
  254. package/dist/lib/seed-hash.js +13 -0
  255. package/dist/lib/tanstack-highlight.d.ts +4 -0
  256. package/dist/lib/tanstack-highlight.js +41 -0
  257. package/dist/schema-form.d.ts +120 -0
  258. package/dist/schema-form.js +129 -0
  259. package/dist/styles.css +18814 -0
  260. package/docs/api/README.md +134 -0
  261. package/docs/api/blocks/app-bar.md +227 -0
  262. package/docs/api/blocks/app-status-bar.md +257 -0
  263. package/docs/api/blocks/async-state.md +53 -0
  264. package/docs/api/blocks/card-grid.md +31 -0
  265. package/docs/api/blocks/command-menu.md +89 -0
  266. package/docs/api/blocks/composer.md +40 -0
  267. package/docs/api/blocks/empty-state.md +97 -0
  268. package/docs/api/blocks/faq-chat.md +147 -0
  269. package/docs/api/blocks/game-share-card.md +40 -0
  270. package/docs/api/blocks/launcher-grid.md +86 -0
  271. package/docs/api/blocks/listing-detail.md +51 -0
  272. package/docs/api/blocks/loading-state.md +21 -0
  273. package/docs/api/blocks/mosaic.md +94 -0
  274. package/docs/api/blocks/permission-grid.md +79 -0
  275. package/docs/api/blocks/pricing-card.md +90 -0
  276. package/docs/api/blocks/release-tag.md +62 -0
  277. package/docs/api/blocks/resource-select.md +126 -0
  278. package/docs/api/blocks/result-state.md +42 -0
  279. package/docs/api/blocks/section-card.md +42 -0
  280. package/docs/api/blocks/section-heading.md +61 -0
  281. package/docs/api/blocks/spec-sheet.md +400 -0
  282. package/docs/api/blocks/status-tray.md +141 -0
  283. package/docs/api/blocks/upload-queue.md +110 -0
  284. package/docs/api/components/action-row.md +51 -0
  285. package/docs/api/components/activity-line.md +103 -0
  286. package/docs/api/components/alert.md +60 -0
  287. package/docs/api/components/badge.md +77 -0
  288. package/docs/api/components/button.md +94 -0
  289. package/docs/api/components/card.md +165 -0
  290. package/docs/api/components/chat-bubble.md +66 -0
  291. package/docs/api/components/checkbox-bead.md +20 -0
  292. package/docs/api/components/checkbox.md +42 -0
  293. package/docs/api/components/code-input.md +61 -0
  294. package/docs/api/components/combobox.md +303 -0
  295. package/docs/api/components/confirm-action.md +58 -0
  296. package/docs/api/components/copy-field.md +106 -0
  297. package/docs/api/components/count-badge.md +39 -0
  298. package/docs/api/components/cover-pattern.md +43 -0
  299. package/docs/api/components/data-list.md +80 -0
  300. package/docs/api/components/dialog.md +235 -0
  301. package/docs/api/components/diff-view.md +59 -0
  302. package/docs/api/components/disclosure.md +71 -0
  303. package/docs/api/components/dropdown-menu.md +269 -0
  304. package/docs/api/components/field-guide.md +174 -0
  305. package/docs/api/components/field.md +252 -0
  306. package/docs/api/components/game-feedback.md +157 -0
  307. package/docs/api/components/game-hud.md +359 -0
  308. package/docs/api/components/generation-frame.md +59 -0
  309. package/docs/api/components/hold-button.md +44 -0
  310. package/docs/api/components/icon-button.md +56 -0
  311. package/docs/api/components/icon-checkbox.md +42 -0
  312. package/docs/api/components/icon-choice-mark.md +31 -0
  313. package/docs/api/components/icon-lamp.md +92 -0
  314. package/docs/api/components/icon-radio.md +42 -0
  315. package/docs/api/components/inline-code.md +21 -0
  316. package/docs/api/components/input-group.md +53 -0
  317. package/docs/api/components/input.md +54 -0
  318. package/docs/api/components/item-grid.md +262 -0
  319. package/docs/api/components/keycap.md +102 -0
  320. package/docs/api/components/label.md +40 -0
  321. package/docs/api/components/listbox.md +131 -0
  322. package/docs/api/components/loader-blobs.md +9 -0
  323. package/docs/api/components/loader.md +51 -0
  324. package/docs/api/components/markdown.md +91 -0
  325. package/docs/api/components/minimap-labels.md +84 -0
  326. package/docs/api/components/monogram.md +96 -0
  327. package/docs/api/components/motion-visibility.md +9 -0
  328. package/docs/api/components/navigation-rail.md +37 -0
  329. package/docs/api/components/overlay-root.md +92 -0
  330. package/docs/api/components/pixel-number.md +120 -0
  331. package/docs/api/components/popover.md +80 -0
  332. package/docs/api/components/presence.md +106 -0
  333. package/docs/api/components/price-tag.md +48 -0
  334. package/docs/api/components/progress.md +90 -0
  335. package/docs/api/components/prose.md +48 -0
  336. package/docs/api/components/quota-meter.md +47 -0
  337. package/docs/api/components/radio-bead.md +9 -0
  338. package/docs/api/components/radio-group.md +126 -0
  339. package/docs/api/components/resize-handle.md +46 -0
  340. package/docs/api/components/rough-drawing.md +122 -0
  341. package/docs/api/components/rough.md +66 -0
  342. package/docs/api/components/scroll-area.md +22 -0
  343. package/docs/api/components/scroll-motion.md +9 -0
  344. package/docs/api/components/scroll-to-bottom-blob-state.md +162 -0
  345. package/docs/api/components/scroll-to-bottom-blob.md +73 -0
  346. package/docs/api/components/select.md +39 -0
  347. package/docs/api/components/send-action.md +58 -0
  348. package/docs/api/components/separator.md +38 -0
  349. package/docs/api/components/skeleton.md +31 -0
  350. package/docs/api/components/slider.md +70 -0
  351. package/docs/api/components/stat.md +42 -0
  352. package/docs/api/components/status-bead.md +19 -0
  353. package/docs/api/components/status-mark.md +19 -0
  354. package/docs/api/components/status.md +105 -0
  355. package/docs/api/components/store-card.md +53 -0
  356. package/docs/api/components/surface.md +161 -0
  357. package/docs/api/components/switch-bead.md +9 -0
  358. package/docs/api/components/switch.md +40 -0
  359. package/docs/api/components/tabs.md +138 -0
  360. package/docs/api/components/technonomicon.md +773 -0
  361. package/docs/api/components/textarea.md +40 -0
  362. package/docs/api/components/toaster.md +29 -0
  363. package/docs/api/components/tool-call-chip.md +68 -0
  364. package/docs/api/components/tooltip.md +113 -0
  365. package/docs/api/components/trading-card.md +95 -0
  366. package/docs/api/components/undo-action.md +117 -0
  367. package/docs/api/components/undo-toast.md +49 -0
  368. package/docs/api/components/visually-hidden.md +37 -0
  369. package/docs/api/components/window.md +291 -0
  370. package/docs/api/components/wizard.md +65 -0
  371. package/docs/api/enhance/cigarette-card.md +52 -0
  372. package/docs/api/enhance/sketch.md +42 -0
  373. package/docs/api/form.md +1623 -0
  374. package/docs/api/lib/cn.md +46 -0
  375. package/docs/api/lib/format-money.md +30 -0
  376. package/docs/api/lib/glide.md +36 -0
  377. package/docs/api/lib/ime.md +29 -0
  378. package/docs/api/lib/motion-spring.md +91 -0
  379. package/docs/api/lib/motion.md +212 -0
  380. package/docs/api/lib/reorder-motion.md +46 -0
  381. package/docs/api/lib/seed-hash.md +31 -0
  382. package/docs/api/lib/tanstack-highlight.md +40 -0
  383. package/package.json +597 -3
  384. package/src/app-status-bar.css +158 -0
  385. package/src/fonts.css +1 -0
  386. package/src/status-tray.css +282 -0
  387. package/src/styles.css +10177 -0
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useRef, } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { canAnimate, motionEasing, readMotion } from "../lib/motion.js";
5
+ import { Button } from "./button.js";
6
+ import { VisuallyHidden } from "./visually-hidden.js";
7
+ const confirmKeys = new Set(["Enter", " "]);
8
+ /**
9
+ * For an action nothing can undo: press and hold (pointer, Space or Enter)
10
+ * while a fill rises; let go early and it drops back; hold to the top and the
11
+ * action runs. Under reduced motion the fill rises in three steps.
12
+ */
13
+ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm, holdMs = 900, holdHint = "Press and hold to confirm.", className, disabled, loading = false, result, resultLabel, onBlur, onKeyDown, onKeyUp, onPointerDown, onPointerUp, onPointerLeave, onPointerCancel, ...props }, ref) {
14
+ const fill = useRef(null);
15
+ const rising = useRef(null);
16
+ const timer = useRef(null);
17
+ const confirm = useRef(onConfirm);
18
+ const hint = useId();
19
+ useEffect(() => {
20
+ confirm.current = onConfirm;
21
+ }, [onConfirm]);
22
+ useEffect(() => () => {
23
+ rising.current?.cancel();
24
+ if (timer.current !== null)
25
+ clearTimeout(timer.current);
26
+ }, []);
27
+ const finish = () => {
28
+ rising.current = null;
29
+ timer.current = null;
30
+ confirm.current();
31
+ const element = fill.current;
32
+ if (!element || !canAnimate(element))
33
+ return;
34
+ const motion = readMotion(element);
35
+ element.animate([{ opacity: 1 }, { opacity: 0 }], {
36
+ duration: motion.normal,
37
+ easing: motionEasing(motion, motion.easeExit),
38
+ });
39
+ };
40
+ const start = () => {
41
+ const element = fill.current;
42
+ if (disabled === true ||
43
+ loading ||
44
+ !element ||
45
+ rising.current ||
46
+ timer.current)
47
+ return;
48
+ if (!canAnimate(element)) {
49
+ timer.current = setTimeout(finish, holdMs);
50
+ return;
51
+ }
52
+ const motion = readMotion(element);
53
+ const animation = element.animate([{ transform: "scaleY(0)" }, { transform: "scaleY(1)" }], {
54
+ duration: holdMs,
55
+ easing: motion.reduced
56
+ ? "steps(3, end)"
57
+ : motionEasing(motion, "cubic-bezier(0.3, 0, 0.7, 1)"),
58
+ });
59
+ animation.addEventListener("finish", finish);
60
+ rising.current = animation;
61
+ };
62
+ const release = () => {
63
+ if (timer.current !== null) {
64
+ clearTimeout(timer.current);
65
+ timer.current = null;
66
+ }
67
+ const animation = rising.current;
68
+ const element = fill.current;
69
+ if (!animation || !element)
70
+ return;
71
+ rising.current = null;
72
+ // Drop back from wherever the fill had reached.
73
+ const reached = getComputedStyle(element).transform;
74
+ animation.cancel();
75
+ const motion = readMotion(element);
76
+ element.animate([
77
+ { transform: reached === "none" ? "scaleY(0)" : reached },
78
+ { transform: "scaleY(0)" },
79
+ ], { duration: motion.fast, easing: motionEasing(motion, motion.ease) });
80
+ };
81
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { ...props, ref: ref, disabled: disabled, loading: loading, result: result,
82
+ // Only the label repeats on the result face; the hold fill owns one ref.
83
+ resultLabel: resultLabel ?? (result === undefined ? undefined : children), "aria-describedby": hint, className: cn("caps-hold-button", className), onBlur: (event) => {
84
+ release();
85
+ onBlur?.(event);
86
+ }, onPointerDown: (event) => {
87
+ onPointerDown?.(event);
88
+ if (event.button === 0)
89
+ start();
90
+ }, onPointerUp: (event) => {
91
+ onPointerUp?.(event);
92
+ release();
93
+ }, onPointerLeave: (event) => {
94
+ onPointerLeave?.(event);
95
+ release();
96
+ }, onPointerCancel: (event) => {
97
+ onPointerCancel?.(event);
98
+ release();
99
+ }, onKeyDown: (event) => {
100
+ onKeyDown?.(event);
101
+ if (event.key === "Escape")
102
+ release();
103
+ if (!confirmKeys.has(event.key))
104
+ return;
105
+ event.preventDefault();
106
+ if (!event.repeat)
107
+ start();
108
+ }, onKeyUp: (event) => {
109
+ onKeyUp?.(event);
110
+ if (!confirmKeys.has(event.key))
111
+ return;
112
+ event.preventDefault();
113
+ release();
114
+ }, children: [_jsx("span", { ref: fill, "aria-hidden": "true", className: "caps-hold-fill" }), children] }), _jsx(VisuallyHidden, { id: hint, children: holdHint })] }));
115
+ });
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ import { type ButtonProps } from "./button.js";
3
+ /** Supported IconButton glyph sizes. */
4
+ export type IconButtonIconSize = "xs" | "sm" | "md" | "lg";
5
+ /** Props accepted by IconButton. */
6
+ export type IconButtonProps = {
7
+ "aria-label": string;
8
+ active?: boolean;
9
+ icon: ReactNode;
10
+ iconSize?: IconButtonIconSize;
11
+ reveal?: boolean;
12
+ size?: ButtonProps["size"] | "responsive";
13
+ tone?: "default" | "destructive";
14
+ } & Omit<ButtonProps, "children" | "size">;
15
+ /** A compact button for an icon control with an accessible name supplied by the caller. */
16
+ export declare const IconButton: import("react").ForwardRefExoticComponent<{
17
+ "aria-label": string;
18
+ active?: boolean;
19
+ icon: ReactNode;
20
+ iconSize?: IconButtonIconSize;
21
+ reveal?: boolean;
22
+ size?: ButtonProps["size"] | "responsive";
23
+ tone?: "default" | "destructive";
24
+ } & Omit<ButtonProps, "children" | "size"> & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { Button } from "./button.js";
5
+ const iconSizeClasses = {
6
+ xs: "size-3",
7
+ sm: "size-[var(--stipe-icon-size-sm)]",
8
+ md: "size-[var(--stipe-icon-size-md)]",
9
+ lg: "size-[var(--stipe-icon-size-lg)]",
10
+ };
11
+ /** A compact button for an icon control with an accessible name supplied by the caller. */
12
+ export const IconButton = forwardRef(function IconButton({ active = false, icon, iconSize = "xs", className, reveal = false, size = "xs", tone = "default", variant = "ghost", ...props }, ref) {
13
+ const responsive = size === "responsive";
14
+ return (_jsx(Button, { ...props,
15
+ // A result beat on an icon shows the check alone.
16
+ resultLabel: props.result === undefined ? undefined : null, ref: ref, size: responsive ? "sm" : size, variant: variant, className: cn("caps-icon-button btn-circle", responsive && "caps-icon-button-responsive md:btn-xs", active && "text-accent", reveal &&
17
+ "scale-0 transition-transform group-hover:scale-100 focus-visible:scale-100", tone === "destructive" && "hover:text-error", className), children: _jsx("span", { "aria-hidden": "true", className: cn("inline-flex shrink-0 [&>svg]:size-full [&>svg]:[stroke-width:var(--stipe-icon-stroke)]", iconSizeClasses[iconSize]), children: icon }) }));
18
+ });
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from "react";
2
+ import { type CheckboxProps } from "./checkbox.js";
3
+ /** Props accepted by IconCheckbox. The full label names the native input. */
4
+ export type IconCheckboxProps = {
5
+ label: string;
6
+ icon: ReactNode;
7
+ /** A short visible legend; it does not replace the accessible label. */
8
+ caption?: string;
9
+ /** Compact instruments show the icon and caption without a tile or lamp. */
10
+ appearance?: "default" | "compact";
11
+ } & Omit<CheckboxProps, "children" | "indicator">;
12
+ /** An independent icon choice composed from the native Caps Checkbox. */
13
+ export declare const IconCheckbox: import("react").ForwardRefExoticComponent<{
14
+ label: string;
15
+ icon: ReactNode;
16
+ /** A short visible legend; it does not replace the accessible label. */
17
+ caption?: string;
18
+ /** Compact instruments show the icon and caption without a tile or lamp. */
19
+ appearance?: "default" | "compact";
20
+ } & Omit<CheckboxProps, "children" | "indicator"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { Checkbox } from "./checkbox.js";
5
+ import { IconChoiceMark } from "./icon-choice-mark.js";
6
+ /** An independent icon choice composed from the native Caps Checkbox. */
7
+ export const IconCheckbox = forwardRef(function IconCheckbox({ label, icon, caption, appearance, className, ...props }, ref) {
8
+ return (_jsx("label", { className: "caps-icon-choice", "data-appearance": appearance === "compact" ? appearance : undefined, children: _jsx(Checkbox, { ...props, appearance: appearance ?? "default", "aria-label": label, className: cn("caps-icon-choice-input", className), indicator: _jsx(IconChoiceMark, { icon: icon, caption: caption }), ref: ref }) }));
9
+ });
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ /** The decorative face shared by icon choices; its native input names it. */
3
+ export declare function IconChoiceMark({ icon, caption, }: {
4
+ icon: ReactNode;
5
+ caption?: string | undefined;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** The decorative face shared by icon choices; its native input names it. */
3
+ export function IconChoiceMark({ icon, caption, }) {
4
+ return (_jsxs("span", { className: "caps-icon-choice-mark", "aria-hidden": "true", children: [_jsx("span", { className: "caps-icon-choice-art", children: icon }), caption ? (_jsx("span", { className: "caps-icon-choice-caption", children: caption })) : null, _jsx("span", { className: "caps-icon-choice-lamp" })] }));
5
+ }
@@ -0,0 +1,42 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ /** Props accepted by IconLamp. */
3
+ export type IconLampProps = {
4
+ /** The full name read with the state, such as "Read files". */
5
+ label: string;
6
+ icon: ReactNode;
7
+ /** A short visible legend; it does not replace the accessible label. */
8
+ caption?: string;
9
+ /** Lit: the icon in colour with the ink lamp. Off: the grey silhouette. */
10
+ on: boolean;
11
+ /** How the state reads aloud, as [on, off]. */
12
+ stateLabels?: readonly [string, string];
13
+ } & Omit<HTMLAttributes<HTMLElement>, "children">;
14
+ /**
15
+ * A read-only icon state: an icon key's face with no input, for rights or
16
+ * settings shown but not changed here. It reads as "Label: state".
17
+ */
18
+ export declare const IconLamp: import("react").ForwardRefExoticComponent<{
19
+ /** The full name read with the state, such as "Read files". */
20
+ label: string;
21
+ icon: ReactNode;
22
+ /** A short visible legend; it does not replace the accessible label. */
23
+ caption?: string;
24
+ /** Lit: the icon in colour with the ink lamp. Off: the grey silhouette. */
25
+ on: boolean;
26
+ /** How the state reads aloud, as [on, off]. */
27
+ stateLabels?: readonly [string, string];
28
+ } & Omit<HTMLAttributes<HTMLElement>, "children"> & import("react").RefAttributes<HTMLElement>>;
29
+ /** One lamp in an IconLamps row. */
30
+ export type IconLampItem = Omit<IconLampProps, "stateLabels"> & {
31
+ id: string;
32
+ };
33
+ /** Props accepted by IconLamps. */
34
+ export type IconLampsProps = {
35
+ items: readonly IconLampItem[];
36
+ stateLabels?: readonly [string, string];
37
+ } & Omit<HTMLAttributes<HTMLUListElement>, "children">;
38
+ /** A named row of read-only icon lamps; name it with `aria-label(ledby)`. */
39
+ export declare const IconLamps: import("react").ForwardRefExoticComponent<{
40
+ items: readonly IconLampItem[];
41
+ stateLabels?: readonly [string, string];
42
+ } & Omit<HTMLAttributes<HTMLUListElement>, "children"> & import("react").RefAttributes<HTMLUListElement>>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { IconChoiceMark } from "./icon-choice-mark.js";
5
+ /**
6
+ * A read-only icon state: an icon key's face with no input, for rights or
7
+ * settings shown but not changed here. It reads as "Label: state".
8
+ */
9
+ export const IconLamp = forwardRef(function IconLamp({ label, icon, caption, on, stateLabels = ["on", "off"], className, ...props }, ref) {
10
+ return (_jsxs("span", { ...props, className: cn("caps-icon-lamp", className), "data-on": on, ref: ref, children: [_jsx(IconChoiceMark, { icon: icon, caption: caption }), _jsxs("span", { className: "sr-only", children: [label, ": ", on ? stateLabels[0] : stateLabels[1]] })] }));
11
+ });
12
+ /** A named row of read-only icon lamps; name it with `aria-label(ledby)`. */
13
+ export const IconLamps = forwardRef(function IconLamps({ items, stateLabels, className, ...props }, ref) {
14
+ return (_jsx("ul", { ...props, className: cn("caps-icon-lamps", className), ref: ref, children: items.map(({ id, ...item }) => (_jsx("li", { children: _jsx(IconLamp, { ...item, ...(stateLabels === undefined ? {} : { stateLabels }) }) }, id))) }));
15
+ });
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from "react";
2
+ import { type RadioGroupItemProps } from "./radio-group.js";
3
+ /** Props accepted by IconRadio. Use it inside the existing RadioGroup. */
4
+ export type IconRadioProps = {
5
+ label: string;
6
+ icon: ReactNode;
7
+ /** A short visible legend; it does not replace the accessible label. */
8
+ caption?: string;
9
+ } & Omit<RadioGroupItemProps, "children" | "indicator">;
10
+ /** One icon choice in a native, mutually exclusive Caps RadioGroup. */
11
+ export declare const IconRadio: import("react").ForwardRefExoticComponent<{
12
+ label: string;
13
+ icon: ReactNode;
14
+ /** A short visible legend; it does not replace the accessible label. */
15
+ caption?: string;
16
+ } & Omit<RadioGroupItemProps, "children" | "indicator"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { RadioGroupItem } from "./radio-group.js";
5
+ import { IconChoiceMark } from "./icon-choice-mark.js";
6
+ /** One icon choice in a native, mutually exclusive Caps RadioGroup. */
7
+ export const IconRadio = forwardRef(function IconRadio({ label, icon, caption, className, ...props }, ref) {
8
+ return (_jsx("label", { className: "caps-icon-choice", children: _jsx(RadioGroupItem, { ...props, "aria-label": label, className: cn("caps-icon-choice-input", className), indicator: _jsx(IconChoiceMark, { icon: icon, caption: caption }), ref: ref }) }));
9
+ });
@@ -0,0 +1,3 @@
1
+ import { type HTMLAttributes } from "react";
2
+ /** A subdued inline code value for identifiers and other technical labels. */
3
+ export declare const InlineCode: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLElement> & import("react").RefAttributes<HTMLElement>>;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ /** A subdued inline code value for identifiers and other technical labels. */
5
+ export const InlineCode = forwardRef(function InlineCode({ className, ...props }, ref) {
6
+ return (_jsx("code", { ...props, className: cn("caps-inline-code", className), ref: ref }));
7
+ });
@@ -0,0 +1,9 @@
1
+ import { type HTMLAttributes } from "react";
2
+ /** Props accepted by InputGroup. */
3
+ export type InputGroupProps = HTMLAttributes<HTMLDivElement>;
4
+ /** Join a control with caller-owned prefixes, suffixes, or actions. */
5
+ export declare const InputGroup: import("react").ForwardRefExoticComponent<InputGroupProps & import("react").RefAttributes<HTMLDivElement>>;
6
+ /** Props accepted by InputGroupAddon. */
7
+ export type InputGroupAddonProps = HTMLAttributes<HTMLDivElement>;
8
+ /** An addon region for an InputGroup. */
9
+ export declare const InputGroupAddon: import("react").ForwardRefExoticComponent<InputGroupAddonProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Children, cloneElement, forwardRef, isValidElement, } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ /** Join a control with caller-owned prefixes, suffixes, or actions. */
5
+ export const InputGroup = forwardRef(function InputGroup({ children, className, ...props }, ref) {
6
+ return (_jsx("div", { ...props, className: cn("caps-input-group join flex w-full items-stretch [&>*]:join-item [&>*:first-child]:rounded-e-none [&>*:last-child]:rounded-s-none [&>*:not(:first-child):not(:last-child)]:rounded-none [&>.btn]:shrink-0 [&>.input]:min-w-0 [&>.input]:flex-1 [&>.select]:min-w-0 [&>.select]:flex-1", className), ref: ref, children: Children.map(children, (child) => isValidElement(child)
7
+ ? cloneElement(child, {
8
+ className: cn("join-item caps-input-group-control", child.props.className),
9
+ })
10
+ : child) }));
11
+ });
12
+ /** An addon region for an InputGroup. */
13
+ export const InputGroupAddon = forwardRef(function InputGroupAddon({ className, ...props }, ref) {
14
+ return (_jsx("div", { ...props, className: cn("join-item flex items-center border border-base-300 bg-base-200 px-md text-sm text-base-content", className), ref: ref }));
15
+ });
@@ -0,0 +1,15 @@
1
+ import { type ReactNode, type InputHTMLAttributes } from "react";
2
+ /** Props accepted by Input. */
3
+ export type InputProps = {
4
+ invalid?: boolean;
5
+ } & InputHTMLAttributes<HTMLInputElement>;
6
+ /** A styled native text input that preserves caller-owned value and event behavior. */
7
+ export declare const Input: import("react").ForwardRefExoticComponent<{
8
+ invalid?: boolean;
9
+ } & InputHTMLAttributes<HTMLInputElement> & import("react").RefAttributes<HTMLInputElement>>;
10
+ /** A native search input with an optional decorative leading icon. */
11
+ export declare const SearchInput: import("react").ForwardRefExoticComponent<{
12
+ invalid?: boolean;
13
+ } & InputHTMLAttributes<HTMLInputElement> & {
14
+ leading?: ReactNode;
15
+ } & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ /** A styled native text input that preserves caller-owned value and event behavior. */
5
+ export const Input = forwardRef(function Input({ className, invalid, ...props }, ref) {
6
+ return (_jsx("input", { ...props, "aria-invalid": invalid
7
+ ? true
8
+ : props["aria-invalid"] === false
9
+ ? undefined
10
+ : props["aria-invalid"], className: cn("input caps-input w-full", className), ref: ref }));
11
+ });
12
+ /** A native search input with an optional decorative leading icon. */
13
+ export const SearchInput = forwardRef(function SearchInput({ leading, className, ...props }, ref) {
14
+ return (_jsxs("span", { className: "caps-search-input", "data-leading": leading ? "true" : undefined, children: [leading ? (_jsx("span", { className: "caps-search-leading", "aria-hidden": "true", children: leading })) : null, _jsx(Input, { ...props, type: "search", className: className, ref: ref })] }));
15
+ });
@@ -0,0 +1,110 @@
1
+ import { type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from "react";
2
+ /** One item placed on the grid. Coordinates are cells from the top left. */
3
+ export type ItemGridItem = {
4
+ id: string;
5
+ /** Accessible name, e.g. "Axe" or "Bread". */
6
+ label: string;
7
+ x: number;
8
+ y: number;
9
+ /** Footprint width in cells after rotation. Defaults to 1. */
10
+ w?: number;
11
+ /** Footprint height in cells after rotation. Defaults to 1. */
12
+ h?: number;
13
+ /** Decorative art: a sprite, an image or an SVG. */
14
+ icon?: ReactNode;
15
+ /** Stack count, drawn in pixel numerals when above 1. */
16
+ quantity?: number;
17
+ selected?: boolean;
18
+ };
19
+ /** A drop preview: where the held item would land, and whether it may. */
20
+ export type ItemGridGhost = {
21
+ x: number;
22
+ y: number;
23
+ w: number;
24
+ h: number;
25
+ legal: boolean;
26
+ /** Why an illegal drop is refused, in the host's words. */
27
+ reason?: string;
28
+ };
29
+ /** A grid cell, with the item covering it if any. */
30
+ export type ItemGridCell = {
31
+ x: number;
32
+ y: number;
33
+ item: ItemGridItem | null;
34
+ };
35
+ /** Cell sizes: pockets, a backpack and a big chest view. */
36
+ export type ItemGridCellSize = "sm" | "md" | "lg";
37
+ /** Props accepted by ItemGrid. */
38
+ export type ItemGridProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
39
+ /** Accessible name of the container, e.g. "Backpack". */
40
+ label: string;
41
+ columns: number;
42
+ rows: number;
43
+ items: readonly ItemGridItem[];
44
+ ghost?: ItemGridGhost | null;
45
+ cellSize?: ItemGridCellSize;
46
+ /** Controlled keyboard cursor. Omit to let the grid keep its own. */
47
+ activeCell?: {
48
+ x: number;
49
+ y: number;
50
+ };
51
+ /** The keyboard cursor moved to another cell. */
52
+ onActiveCellChange?: (cell: ItemGridCell) => void;
53
+ /** Click, Enter or Space on a cell. The host decides pick up, drop or use. */
54
+ onCellActivate?: (cell: ItemGridCell) => void;
55
+ /** Pointer over a cell, or `null` on leaving the grid; for drag ghosts. */
56
+ onCellHover?: (cell: ItemGridCell | null) => void;
57
+ };
58
+ /**
59
+ * A Diablo-style item grid. Cells carry the keyboard and pointer interface;
60
+ * items and the drop ghost are drawn above them from the host's facts. The
61
+ * grid never moves, merges or validates items: it shows what the host says.
62
+ */
63
+ export declare const ItemGrid: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
64
+ /** Accessible name of the container, e.g. "Backpack". */
65
+ label: string;
66
+ columns: number;
67
+ rows: number;
68
+ items: readonly ItemGridItem[];
69
+ ghost?: ItemGridGhost | null;
70
+ cellSize?: ItemGridCellSize;
71
+ /** Controlled keyboard cursor. Omit to let the grid keep its own. */
72
+ activeCell?: {
73
+ x: number;
74
+ y: number;
75
+ };
76
+ /** The keyboard cursor moved to another cell. */
77
+ onActiveCellChange?: (cell: ItemGridCell) => void;
78
+ /** Click, Enter or Space on a cell. The host decides pick up, drop or use. */
79
+ onCellActivate?: (cell: ItemGridCell) => void;
80
+ /** Pointer over a cell, or `null` on leaving the grid; for drag ghosts. */
81
+ onCellHover?: (cell: ItemGridCell | null) => void;
82
+ } & import("react").RefAttributes<HTMLDivElement>>;
83
+ /** How a slot answers the item being held over it. */
84
+ export type ItemSlotState = "idle" | "legal" | "illegal";
85
+ /** Props accepted by ItemSlot. */
86
+ export type ItemSlotProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
87
+ /** Slot name, e.g. "Head" or "Main hand". */
88
+ label: string;
89
+ /** The equipped item, if any. */
90
+ item?: {
91
+ label: string;
92
+ icon?: ReactNode;
93
+ } | null;
94
+ /** Faint outline art shown while the slot is empty. */
95
+ placeholder?: ReactNode;
96
+ state?: ItemSlotState;
97
+ };
98
+ /** One equipment slot for a paper doll: a pocket, its item and its name. */
99
+ export declare const ItemSlot: import("react").ForwardRefExoticComponent<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
100
+ /** Slot name, e.g. "Head" or "Main hand". */
101
+ label: string;
102
+ /** The equipped item, if any. */
103
+ item?: {
104
+ label: string;
105
+ icon?: ReactNode;
106
+ } | null;
107
+ /** Faint outline art shown while the slot is empty. */
108
+ placeholder?: ReactNode;
109
+ state?: ItemSlotState;
110
+ } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,136 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useId, useState, } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { PixelNumber } from "./pixel-number.js";
5
+ const MAX_CELLS = 32;
6
+ function footprint(item) {
7
+ return { w: item.w ?? 1, h: item.h ?? 1 };
8
+ }
9
+ /** Throw on geometry the grid cannot draw; the host's data is wrong. */
10
+ function checkGrid(columns, rows, items) {
11
+ for (const [name, value] of [
12
+ ["columns", columns],
13
+ ["rows", rows],
14
+ ])
15
+ if (!Number.isInteger(value) || value < 1 || value > MAX_CELLS)
16
+ throw new RangeError(`ItemGrid ${name} must be an integer from 1 to ${String(MAX_CELLS)}`);
17
+ const occupant = new Map();
18
+ for (const item of items) {
19
+ const { w, h } = footprint(item);
20
+ if (![item.x, item.y, w, h].every(Number.isInteger) ||
21
+ item.x < 0 ||
22
+ item.y < 0 ||
23
+ w < 1 ||
24
+ h < 1 ||
25
+ item.x + w > columns ||
26
+ item.y + h > rows)
27
+ throw new RangeError(`ItemGrid item ${item.id} does not fit a ${String(columns)}×${String(rows)} grid`);
28
+ for (let y = item.y; y < item.y + h; y++)
29
+ for (let x = item.x; x < item.x + w; x++)
30
+ occupant.set(`${String(x)},${String(y)}`, item);
31
+ }
32
+ return occupant;
33
+ }
34
+ /** The cell a navigation key moves to, clamped to the grid. */
35
+ function keyMove(key, { x, y }, columns, rows) {
36
+ switch (key) {
37
+ case "ArrowLeft":
38
+ return [Math.max(0, x - 1), y];
39
+ case "ArrowRight":
40
+ return [Math.min(columns - 1, x + 1), y];
41
+ case "ArrowUp":
42
+ return [x, Math.max(0, y - 1)];
43
+ case "ArrowDown":
44
+ return [x, Math.min(rows - 1, y + 1)];
45
+ case "Home":
46
+ return [0, y];
47
+ case "End":
48
+ return [columns - 1, y];
49
+ default:
50
+ return null;
51
+ }
52
+ }
53
+ function area(x, y, w, h) {
54
+ return {
55
+ gridColumn: `${String(x + 1)} / span ${String(w)}`,
56
+ gridRow: `${String(y + 1)} / span ${String(h)}`,
57
+ };
58
+ }
59
+ function tracks(style, columns, rows) {
60
+ return {
61
+ ...style,
62
+ "--caps-item-grid-columns": columns,
63
+ "--caps-item-grid-rows": rows,
64
+ };
65
+ }
66
+ function cellName(cell, ghost) {
67
+ const where = `column ${String(cell.x + 1)}, row ${String(cell.y + 1)}`;
68
+ const inGhost = ghost &&
69
+ cell.x >= ghost.x &&
70
+ cell.x < ghost.x + ghost.w &&
71
+ cell.y >= ghost.y &&
72
+ cell.y < ghost.y + ghost.h;
73
+ const drop = inGhost
74
+ ? ghost.legal
75
+ ? ", drop here"
76
+ : `, cannot drop${ghost.reason ? `: ${ghost.reason}` : ""}`
77
+ : "";
78
+ if (!cell.item)
79
+ return `Empty, ${where}${drop}`;
80
+ const count = cell.item.quantity !== undefined && cell.item.quantity > 1
81
+ ? ` ×${String(cell.item.quantity)}`
82
+ : "";
83
+ return `${cell.item.label}${count}, ${where}${drop}`;
84
+ }
85
+ /**
86
+ * A Diablo-style item grid. Cells carry the keyboard and pointer interface;
87
+ * items and the drop ghost are drawn above them from the host's facts. The
88
+ * grid never moves, merges or validates items: it shows what the host says.
89
+ */
90
+ export const ItemGrid = forwardRef(function ItemGrid({ label, columns, rows, items, ghost = null, cellSize = "md", activeCell, onActiveCellChange, onCellActivate, onCellHover, className, style, ...props }, ref) {
91
+ const occupant = checkGrid(columns, rows, items);
92
+ const [ownCell, setOwnCell] = useState({ x: 0, y: 0 });
93
+ const current = activeCell ?? ownCell;
94
+ const reasonId = useId();
95
+ const cellAt = (x, y) => ({
96
+ x,
97
+ y,
98
+ item: occupant.get(`${String(x)},${String(y)}`) ?? null,
99
+ });
100
+ const focusCell = (grid, x, y) => {
101
+ const next = cellAt(x, y);
102
+ if (activeCell === undefined)
103
+ setOwnCell({ x, y });
104
+ onActiveCellChange?.(next);
105
+ grid
106
+ .querySelector(`[data-cell="${String(x)},${String(y)}"]`)
107
+ ?.focus({ preventScroll: true });
108
+ };
109
+ const onKeyDown = (event) => {
110
+ const move = keyMove(event.key, current, columns, rows);
111
+ if (move) {
112
+ event.preventDefault();
113
+ focusCell(event.currentTarget, move[0], move[1]);
114
+ }
115
+ else if (event.key === "Enter" || event.key === " ") {
116
+ event.preventDefault();
117
+ onCellActivate?.(cellAt(current.x, current.y));
118
+ }
119
+ };
120
+ const refused = ghost && !ghost.legal && ghost.reason ? ghost.reason : null;
121
+ return (_jsxs("div", { ...props, ref: ref, className: cn("caps-item-grid", className), "data-cell-size": cellSize, style: tracks(style, columns, rows), children: [_jsxs("div", { className: "caps-item-grid-board", children: [_jsx("div", { role: "grid", "aria-label": label, "aria-rowcount": rows, "aria-colcount": columns, "aria-describedby": refused ? reasonId : undefined, className: "caps-item-grid-cells", onKeyDown: onKeyDown, onPointerLeave: () => onCellHover?.(null), children: Array.from({ length: rows }, (_, y) => (_jsx("div", { role: "row", className: "caps-item-grid-row", children: Array.from({ length: columns }, (_, x) => {
122
+ const cell = cellAt(x, y);
123
+ const active = current.x === x && current.y === y;
124
+ return (_jsx("div", { role: "gridcell", tabIndex: active ? 0 : -1, "aria-label": cellName(cell, ghost), "aria-selected": cell.item?.selected ?? false, "data-cell": `${String(x)},${String(y)}`, "data-occupied": cell.item !== null, className: "caps-item-grid-cell", onClick: (event) => {
125
+ focusCell(event.currentTarget.closest("[role=grid]") ?? event.currentTarget, x, y);
126
+ onCellActivate?.(cell);
127
+ }, onPointerEnter: () => onCellHover?.(cell) }, x));
128
+ }) }, y))) }), _jsxs("div", { className: "caps-item-grid-items", "aria-hidden": "true", children: [items.map((item) => {
129
+ const { w, h } = footprint(item);
130
+ return (_jsxs("div", { className: "caps-item", "data-selected": item.selected ?? false, style: area(item.x, item.y, w, h), children: [_jsx("span", { className: "caps-item-icon", children: item.icon }), item.quantity !== undefined && item.quantity > 1 ? (_jsx(PixelNumber, { className: "caps-item-count", value: item.quantity, size: "sm" })) : null] }, item.id));
131
+ }), ghost ? (_jsx("div", { className: "caps-item-ghost", "data-legal": ghost.legal, style: area(ghost.x, ghost.y, ghost.w, ghost.h) })) : null] })] }), refused ? (_jsx("p", { id: reasonId, className: "caps-item-grid-reason", role: "status", children: refused })) : null] }));
132
+ });
133
+ /** One equipment slot for a paper doll: a pocket, its item and its name. */
134
+ export const ItemSlot = forwardRef(function ItemSlot({ label, item = null, placeholder, state = "idle", className, type = "button", ...props }, ref) {
135
+ return (_jsxs("button", { ...props, ref: ref, type: type, className: cn("caps-item-slot", className), "data-state": state, "data-filled": item !== null, "aria-label": props["aria-label"] ?? `${label}: ${item?.label ?? "empty"}`, children: [_jsx("span", { className: "caps-item-slot-pocket", "aria-hidden": "true", children: item ? (_jsx("span", { className: "caps-item-icon", children: item.icon })) : (_jsx("span", { className: "caps-item-slot-placeholder", children: placeholder })) }), _jsx("span", { className: "caps-item-slot-label", "aria-hidden": "true", children: label })] }));
136
+ });