@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,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { animate } from "animejs";
3
+ import { motionTime, readMotion, useReducedMotion } from "../lib/motion.js";
4
+ import { immediateTiming, themedEase, themedSpring, } from "../lib/motion-spring.js";
5
+ import { useId, useLayoutEffect, useRef } from "react";
6
+ import { GooeyFilterDefinition, GooeyInsetDefinition, GooeyShadowDefinition, GooeySurfaceDefinition, } from "./gooey.js";
7
+ const contours = {
8
+ unchecked: "M3.25 5.9 L5.1 7.9 L8.8 3.8",
9
+ checked: "M3.25 5.9 L5.1 7.9 L8.8 3.8",
10
+ mixed: "M3.4 6 L6 6 L8.6 6",
11
+ };
12
+ const goo = { blurStdDeviation: 0.22, alphaSlope: 18, alphaIntercept: -8 };
13
+ /** @internal Draw one sculpted stroke from its first tip through to its last. */
14
+ export function CheckboxBead({ state }) {
15
+ const id = `caps-checkbox-${useId().replaceAll(":", "")}`;
16
+ const mark = useRef(null);
17
+ const initial = useRef(state).current;
18
+ const previous = useRef(state);
19
+ const reduced = useReducedMotion();
20
+ useLayoutEffect(() => {
21
+ if (!mark.current)
22
+ return;
23
+ const filled = state !== "unchecked";
24
+ const wasFilled = previous.current !== "unchecked";
25
+ const immediate = reduced || previous.current === state;
26
+ previous.current = state;
27
+ const motion = readMotion(mark.current);
28
+ const emptying = themedEase(motion, { ease: "inCubic", duration: 160 });
29
+ const morph = animate(mark.current, {
30
+ d: {
31
+ to: contours[state],
32
+ ...(immediate
33
+ ? immediateTiming
34
+ : themedEase(motion, { ease: "outCubic", duration: 200 })),
35
+ },
36
+ "stroke-dashoffset": {
37
+ to: filled ? 0 : 1,
38
+ ...(immediate
39
+ ? immediateTiming
40
+ : filled
41
+ ? themedEase(motion, { ease: "inOutSine", duration: 260 })
42
+ : emptying),
43
+ },
44
+ "stroke-width": immediate
45
+ ? { to: filled ? 2.4 : 0, duration: 0 }
46
+ : filled && !wasFilled
47
+ ? [
48
+ {
49
+ to: 2.2,
50
+ ...themedEase(motion, { ease: "outCubic", duration: 65 }),
51
+ },
52
+ {
53
+ to: 2.4,
54
+ delay: motionTime(motion, 120),
55
+ ...themedSpring(motion, { bounce: 0.35, duration: 220 }),
56
+ },
57
+ ]
58
+ : { to: filled ? 2.4 : 0, ...emptying },
59
+ });
60
+ return () => {
61
+ morph.cancel();
62
+ };
63
+ }, [state, reduced]);
64
+ return (_jsxs("svg", { className: "caps-checkbox-visual", width: "24", height: "24", viewBox: "0 0 12 12", "aria-hidden": "true", focusable: "false", children: [_jsxs("defs", { children: [_jsx(GooeySurfaceDefinition, { id: `${id}-surface`, transform: "matrix(1 0 0 1 6 6)" }), _jsx(GooeyShadowDefinition, { id: `${id}-shadow` }), _jsx(GooeyInsetDefinition, { id: `${id}-inset` }), _jsx(GooeyFilterDefinition, { id: `${id}-goo`, values: goo }), _jsxs("filter", { id: `${id}-relief`, x: "-25%", y: "-25%", width: "150%", height: "150%", colorInterpolationFilters: "sRGB", children: [_jsx("feGaussianBlur", { in: "SourceAlpha", stdDeviation: "0.28", result: "relief" }), _jsx("feSpecularLighting", { in: "relief", surfaceScale: "1.2", specularConstant: "0.55", specularExponent: "18", lightingColor: "color-mix(in srgb, var(--color-white) calc(var(--depth) * 100%), var(--color-black))", result: "light", children: _jsx("feDistantLight", { azimuth: "225", elevation: "55" }) }), _jsx("feComposite", { in: "light", in2: "SourceAlpha", operator: "in", result: "clipped-light" }), _jsx("feBlend", { in: "SourceGraphic", in2: "clipped-light", mode: "screen" })] }), _jsx("mask", { id: `${id}-contour`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "12", height: "12", style: { maskType: "alpha" }, children: _jsx("path", { ref: mark, d: contours[initial], fill: "none", stroke: "var(--color-white)", strokeWidth: initial === "unchecked" ? 0 : 2.4, pathLength: "1", strokeDasharray: "1", strokeDashoffset: initial === "unchecked" ? 1 : 0, strokeLinecap: "round", strokeLinejoin: "round",
65
+ // A horizontal mixed mark has no height for an object-bounding-box filter.
66
+ filter: state === "mixed" ? undefined : `url(#${id}-goo)` }) }), _jsx("clipPath", { id: `${id}-well`, children: _jsx("rect", { x: "1", y: "1", width: "10", height: "10", rx: "2.5" }) })] }), _jsx("rect", { className: "caps-checkbox-track", x: "0.5", y: "0.5", width: "11", height: "11", rx: "3", filter: `url(#${id}-inset)` }), _jsx("g", { clipPath: `url(#${id}-well)`, children: _jsx("g", { filter: `url(#${id}-shadow)`, children: _jsx("g", { filter: `url(#${id}-relief)`, children: _jsx("g", { mask: `url(#${id}-contour)`, children: _jsx("rect", { x: "0", y: "0", width: "12", height: "12", fill: `url(#${id}-surface)` }) }) }) }) })] }));
67
+ }
@@ -0,0 +1,21 @@
1
+ import { type InputHTMLAttributes, type ReactNode } from "react";
2
+ /** Props accepted by Checkbox. */
3
+ export type CheckboxProps = {
4
+ /** Compact instruments keep the wrapper's focus ring and disabled ink readable. */
5
+ appearance?: "default" | "compact";
6
+ /** Replaces the bead while the native input continues to own selection. */
7
+ indicator?: ReactNode;
8
+ invalid?: boolean;
9
+ /** Shows a mixed selection while checked still controls native form submission. */
10
+ indeterminate?: boolean;
11
+ } & InputHTMLAttributes<HTMLInputElement>;
12
+ /** A native checkbox with styled invalid-state presentation. */
13
+ export declare const Checkbox: import("react").ForwardRefExoticComponent<{
14
+ /** Compact instruments keep the wrapper's focus ring and disabled ink readable. */
15
+ appearance?: "default" | "compact";
16
+ /** Replaces the bead while the native input continues to own selection. */
17
+ indicator?: ReactNode;
18
+ invalid?: boolean;
19
+ /** Shows a mixed selection while checked still controls native form submission. */
20
+ indeterminate?: boolean;
21
+ } & InputHTMLAttributes<HTMLInputElement> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { CheckboxBead } from "./checkbox-bead.js";
5
+ /** A native checkbox with styled invalid-state presentation. */
6
+ export const Checkbox = forwardRef(function Checkbox({ className, appearance, indicator, invalid, indeterminate = false, checked, defaultChecked = false, onChange, ...props }, ref) {
7
+ const input = useRef(null);
8
+ const [localChecked, setLocalChecked] = useState(defaultChecked);
9
+ const selected = checked ?? localChecked;
10
+ const state = indeterminate ? "mixed" : selected ? "checked" : "unchecked";
11
+ useImperativeHandle(ref, () => {
12
+ if (!input.current)
13
+ throw new Error("Checkbox input is not mounted");
14
+ return input.current;
15
+ }, []);
16
+ useLayoutEffect(() => {
17
+ if (input.current)
18
+ input.current.indeterminate = indeterminate;
19
+ }, [indeterminate]);
20
+ useEffect(() => {
21
+ const form = input.current?.form;
22
+ if (!form || checked !== undefined)
23
+ return;
24
+ const reset = (event) => {
25
+ queueMicrotask(() => {
26
+ if (!event.defaultPrevented && input.current)
27
+ setLocalChecked(defaultChecked);
28
+ });
29
+ };
30
+ form.addEventListener("reset", reset);
31
+ return () => {
32
+ form.removeEventListener("reset", reset);
33
+ };
34
+ }, [checked, defaultChecked, props.form]);
35
+ return (_jsxs("span", { className: "caps-checkbox", "data-appearance": appearance === "compact" ? appearance : undefined, "data-state": state, "data-custom-indicator": indicator !== undefined || undefined, children: [_jsx("input", { ...props, checked: selected, "aria-checked": indeterminate ? "mixed" : selected, "aria-invalid": invalid
36
+ ? true
37
+ : props["aria-invalid"] === false
38
+ ? undefined
39
+ : props["aria-invalid"], className: cn("caps-checkbox-input", className), onChange: (event) => {
40
+ if (checked === undefined)
41
+ setLocalChecked(event.target.checked);
42
+ onChange?.(event);
43
+ // The owner clears a controlled mixed state; native activation clears it first.
44
+ event.currentTarget.indeterminate = indeterminate;
45
+ }, ref: input, type: "checkbox" }), indicator !== undefined ? indicator : _jsx(CheckboxBead, { state: state })] }));
46
+ });
@@ -0,0 +1,35 @@
1
+ import { type InputHTMLAttributes } from "react";
2
+ /** What the caller has learned about the entered code. */
3
+ export type CodeInputStatus = "idle" | "invalid" | "accepted";
4
+ /** Props accepted by CodeInput. */
5
+ export type CodeInputProps = {
6
+ /** How many characters the code has. */
7
+ length?: number;
8
+ /** The current code. */
9
+ value: string;
10
+ /** Receives the code after a native edit or a normalized paste. */
11
+ onValueChange: (value: string) => void;
12
+ /**
13
+ * `invalid` shakes the slots in a cascade and marks them; `accepted` lifts
14
+ * them in a wave. Say what was wrong next to the field, as with any error.
15
+ */
16
+ status?: CodeInputStatus;
17
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "maxLength" | "size">;
18
+ /**
19
+ * A short code (a sign-in or pairing code) shown as one slot per character.
20
+ * It is a single native input underneath, so typing, pasting, one-time-code
21
+ * autofill and screen readers behave as they do on any text field.
22
+ */
23
+ export declare const CodeInput: import("react").ForwardRefExoticComponent<{
24
+ /** How many characters the code has. */
25
+ length?: number;
26
+ /** The current code. */
27
+ value: string;
28
+ /** Receives the code after a native edit or a normalized paste. */
29
+ onValueChange: (value: string) => void;
30
+ /**
31
+ * `invalid` shakes the slots in a cascade and marks them; `accepted` lifts
32
+ * them in a wave. Say what was wrong next to the field, as with any error.
33
+ */
34
+ status?: CodeInputStatus;
35
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, "defaultValue" | "onChange" | "size" | "value" | "maxLength"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
3
+ import { cn } from "../lib/cn.js";
4
+ import { canAnimate, motionEasing, readMotion, } from "../lib/motion.js";
5
+ const graphemes = new Intl.Segmenter();
6
+ function land(slot, motion) {
7
+ // A character drops into its slot: a small press, then a settle sized by
8
+ // the theme's bounce. Reduced motion only fades the character in.
9
+ if (motion.reduced)
10
+ return slot.animate([{ color: "transparent" }, {}], {
11
+ duration: motion.fast,
12
+ });
13
+ const give = 0.06 * Math.min(1.5, motion.bounce / 0.3);
14
+ return slot.animate([
15
+ { transform: `translateY(${String(motion.distance / 2)}px) scale(0.94)` },
16
+ { transform: `scale(${String(1 + give)})`, offset: 0.55 },
17
+ { transform: "none" },
18
+ ], {
19
+ duration: motion.springDuration,
20
+ easing: motionEasing(motion, motion.ease),
21
+ });
22
+ }
23
+ function refuse(slot, index, motion) {
24
+ const x = motion.shake;
25
+ return slot.animate([
26
+ { transform: "none" },
27
+ { transform: `translateX(${String(-x)}px)` },
28
+ { transform: `translateX(${String(x)}px)` },
29
+ { transform: `translateX(${String(-x / 2)}px)` },
30
+ { transform: "none" },
31
+ ], {
32
+ duration: motion.slow,
33
+ delay: index * motion.stagger,
34
+ easing: motionEasing(motion, "linear"),
35
+ });
36
+ }
37
+ function accept(slot, index, motion) {
38
+ return slot.animate([
39
+ { transform: "none" },
40
+ { transform: `translateY(${String(-motion.distance)}px)`, offset: 0.4 },
41
+ { transform: "none" },
42
+ ], {
43
+ duration: motion.slow,
44
+ delay: index * motion.stagger,
45
+ easing: motionEasing(motion, motion.ease),
46
+ });
47
+ }
48
+ /**
49
+ * A short code (a sign-in or pairing code) shown as one slot per character.
50
+ * It is a single native input underneath, so typing, pasting, one-time-code
51
+ * autofill and screen readers behave as they do on any text field.
52
+ */
53
+ export const CodeInput = forwardRef(function CodeInput({ length = 6, value, onValueChange, status = "idle", className, onFocus, onBlur, onSelect, onPaste, ...props }, ref) {
54
+ const input = useRef(null);
55
+ const slots = useRef(null);
56
+ useImperativeHandle(ref, () => input.current);
57
+ const [focused, setFocused] = useState(false);
58
+ const [cursor, setCursor] = useState(value.length);
59
+ const characters = Array.from(graphemes.segment(value), (part) => part.segment).slice(0, length);
60
+ const active = Math.min(Array.from(graphemes.segment(value.slice(0, cursor))).length, length - 1);
61
+ const filled = useRef(characters.length);
62
+ useLayoutEffect(() => {
63
+ const root = slots.current;
64
+ const before = filled.current;
65
+ filled.current = characters.length;
66
+ if (!root || !canAnimate(root) || characters.length <= before)
67
+ return;
68
+ const slot = root.children.item(characters.length - 1);
69
+ if (!(slot instanceof HTMLElement))
70
+ return;
71
+ const animation = land(slot, readMotion(root));
72
+ return () => {
73
+ animation.cancel();
74
+ };
75
+ }, [characters.length]);
76
+ useLayoutEffect(() => {
77
+ const root = slots.current;
78
+ if (!root || !canAnimate(root) || status === "idle")
79
+ return;
80
+ const motion = readMotion(root);
81
+ if (motion.reduced)
82
+ return;
83
+ const animations = [...root.children].flatMap((slot, index) => slot instanceof HTMLElement
84
+ ? [
85
+ status === "invalid"
86
+ ? refuse(slot, index, motion)
87
+ : accept(slot, index, motion),
88
+ ]
89
+ : []);
90
+ return () => {
91
+ for (const animation of animations)
92
+ animation.cancel();
93
+ };
94
+ }, [status]);
95
+ return (_jsxs("div", { className: cn("caps-code-input", className), "data-status": status, children: [_jsx("input", { autoComplete: "one-time-code", inputMode: "numeric", spellCheck: false, ...props, "aria-invalid": status === "invalid" ? true : props["aria-invalid"], className: "caps-code-field", maxLength: length, onBlur: (event) => {
96
+ setFocused(false);
97
+ onBlur?.(event);
98
+ }, onChange: (event) => {
99
+ onValueChange(event.target.value.replace(/\s/gu, ""));
100
+ }, onPaste: (event) => {
101
+ onPaste?.(event);
102
+ if (event.defaultPrevented || event.currentTarget.readOnly)
103
+ return;
104
+ const pasted = event.clipboardData.getData("text");
105
+ const normalized = pasted.replace(/\s/gu, "");
106
+ if (normalized === pasted)
107
+ return;
108
+ event.preventDefault();
109
+ const field = event.currentTarget;
110
+ const start = field.selectionStart ?? 0;
111
+ const end = field.selectionEnd ?? start;
112
+ // Native maxlength still owns ordinary edits. Formatted paste is
113
+ // normalized before applying that same remaining input capacity.
114
+ const available = Math.max(0, length - field.value.length + end - start);
115
+ field.setRangeText(normalized.slice(0, available), start, end, "end");
116
+ setCursor(field.selectionStart ?? field.value.length);
117
+ onValueChange(field.value);
118
+ }, onFocus: (event) => {
119
+ setFocused(true);
120
+ setCursor(event.currentTarget.selectionStart ?? value.length);
121
+ onFocus?.(event);
122
+ }, onSelect: (event) => {
123
+ setCursor(event.currentTarget.selectionStart ?? value.length);
124
+ onSelect?.(event);
125
+ }, ref: input, value: characters.join("") }), _jsx("div", { ref: slots, className: "caps-code-slots", "aria-hidden": "true", children: Array.from({ length }, (_, index) => (_jsxs("span", { className: "input caps-input caps-code-slot", "data-active": focused && index === active ? "true" : undefined, "data-filled": index < characters.length ? "true" : undefined, children: [characters[index] ?? "", focused && index === active && characters.length < length ? (_jsx("span", { className: "caps-code-caret" })) : null] }, index))) })] }));
126
+ });
@@ -0,0 +1,86 @@
1
+ import { type ButtonHTMLAttributes, type ReactElement, type ReactNode } from "react";
2
+ import { Popover as BasePopover } from "@base-ui/react/popover";
3
+ /** The same molded field face as Select, for caller-owned selection summaries. */
4
+ export declare const ComboboxTrigger: import("react").ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & import("react").RefAttributes<HTMLButtonElement>>;
5
+ /** Intrinsic focus and selection state supplied when rendering a Combobox item. */
6
+ export type ComboboxItemState = {
7
+ active: boolean;
8
+ disabled: boolean;
9
+ selected: boolean;
10
+ };
11
+ /** Props accepted by Combobox. */
12
+ export type ComboboxProps<T> = ComboboxBaseProps<T> & (ComboboxInlineProps | ComboboxPopupProps) & ComboboxValueProps<T>;
13
+ /** Props shared by every Combobox mode: item shape, rendering, and field wiring. */
14
+ export type ComboboxBaseProps<T> = {
15
+ /** Color intent for the locally active option. */
16
+ activeTone?: "neutral" | "primary";
17
+ /** Native portal destination. Use an unclipped themed container for nested themes. */
18
+ container?: BasePopover.Portal.Props["container"];
19
+ contentClassName?: string;
20
+ emptyMessage?: ReactNode;
21
+ /** Returns the stable identity used to track and compare items. */
22
+ getKey: (item: T) => string;
23
+ /** Returns the text shown for an item in the trigger and the list. */
24
+ getLabel: (item: T) => string;
25
+ /** Returns the text matched against caller input; defaults to the item's label. */
26
+ getSearchText?: (item: T) => string;
27
+ /** Returns the heading an item is grouped under in the list. */
28
+ groupBy?: (item: T) => string;
29
+ /** Identifies the popup trigger or inline input for native label association. */
30
+ id?: string;
31
+ inputProps?: Omit<React.InputHTMLAttributes<HTMLInputElement>, "defaultValue" | "value">;
32
+ inputRef?: React.Ref<HTMLInputElement>;
33
+ /** Receives the selection control's root div. */
34
+ rootRef?: React.Ref<HTMLDivElement>;
35
+ /** Returns whether an item is unselectable. */
36
+ isDisabled?: (item: T) => boolean;
37
+ items: readonly T[];
38
+ placeholder?: string;
39
+ /** Renders one item's content; defaults to its label. */
40
+ renderItem?: (item: T, active: boolean, state: ComboboxItemState) => ReactNode;
41
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue" | "value">;
42
+ /** Combobox mode flags for the inline (always-expanded, no popup trigger) layout. */
43
+ export type ComboboxInlineProps = {
44
+ defaultOpen?: never;
45
+ /** Popup visibility callbacks do not apply to inline choices. */
46
+ onOpenChange?: never;
47
+ open?: never;
48
+ trigger?: never;
49
+ triggerRef?: never;
50
+ };
51
+ /** Combobox mode flags for the popup layout, either caller-controlled or uncontrolled open state. */
52
+ export type ComboboxPopupProps = {
53
+ defaultOpen?: boolean;
54
+ /** Called after the popup's open state changes. */
55
+ onOpenChange?: (open: boolean) => void;
56
+ open?: never;
57
+ trigger: ReactElement<ButtonHTMLAttributes<HTMLButtonElement>>;
58
+ triggerRef?: React.Ref<HTMLButtonElement>;
59
+ } | {
60
+ defaultOpen?: never;
61
+ /** Called after the popup's open state changes. */
62
+ onOpenChange?: (open: boolean) => void;
63
+ open: boolean;
64
+ trigger: ReactElement<ButtonHTMLAttributes<HTMLButtonElement>>;
65
+ triggerRef?: React.Ref<HTMLButtonElement>;
66
+ };
67
+ /** Combobox selection shape: single or multiple, controlled or uncontrolled. */
68
+ export type ComboboxValueProps<T> = (({
69
+ defaultValue?: never;
70
+ value: null | string;
71
+ } | {
72
+ defaultValue?: null | string;
73
+ value?: never;
74
+ }) & {
75
+ multiple?: false;
76
+ /** Called with the newly selected key and item, or null when cleared. */
77
+ onValueChange?: (value: null | string, item: null | T) => void;
78
+ }) | {
79
+ multiple: true;
80
+ value: readonly string[];
81
+ defaultValue?: never;
82
+ /** Called with the newly selected keys and items. */
83
+ onValueChange?: (value: string[], items: T[]) => void;
84
+ };
85
+ /** A searchable selection primitive with caller-owned values and intrinsic keyboard navigation. */
86
+ export declare function Combobox<T>(props: ComboboxProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,218 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isImeComposing } from "../lib/ime.js";
3
+ import { forwardRef, useEffect, useId, useMemo, useState, } from "react";
4
+ import { Popover as BasePopover } from "@base-ui/react/popover";
5
+ import { cn } from "../lib/cn.js";
6
+ import { useGlide } from "../lib/glide.js";
7
+ import { Check, ChevronDown } from "lucide-react";
8
+ import { useOverlayRoot, useOverlayDismiss } from "./overlay-root.js";
9
+ import { Input } from "./input.js";
10
+ import { useSelectableCollection } from "./selectable.js";
11
+ /** The same molded field face as Select, for caller-owned selection summaries. */
12
+ export const ComboboxTrigger = forwardRef(function ComboboxTrigger({ children, className, type = "button", ...props }, ref) {
13
+ return (_jsxs("button", { ...props, ref: ref, type: type, className: cn("select caps-select caps-combobox-trigger w-full", className), children: [_jsx("span", { children: children }), _jsx(ChevronDown, { className: "caps-select-chevron", "aria-hidden": "true" })] }));
14
+ });
15
+ function useComboboxController({ defaultOpen, defaultValue, getKey, getSearchText, groupBy, id, isDisabled, items, onOpenChange, onCommit, multiple, controlledOpen, controlledKeys, trigger, }) {
16
+ const listboxId = `${id}-listbox`;
17
+ const [query, setQuery] = useState("");
18
+ const [keyboardNavigation, setKeyboardNavigation] = useState(false);
19
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
20
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
21
+ const selectedKeys = controlledKeys ?? (uncontrolledValue === null ? [] : [uncontrolledValue]);
22
+ const open = trigger ? (controlledOpen ?? uncontrolledOpen) : true;
23
+ const filtered = useMemo(() => {
24
+ const normalizedQuery = query.trim().toLowerCase();
25
+ return normalizedQuery
26
+ ? items.filter((item) => getSearchText(item).toLowerCase().includes(normalizedQuery))
27
+ : items;
28
+ }, [getSearchText, items, query]);
29
+ const collection = useSelectableCollection({
30
+ getKey,
31
+ isDisabled,
32
+ items: filtered,
33
+ });
34
+ useEffect(() => {
35
+ if (collection.activeKey !== null)
36
+ return;
37
+ const initial = collection.enabledItems.find((item) => selectedKeys.includes(getKey(item))) ?? collection.enabledItems[0];
38
+ if (initial)
39
+ collection.setActiveKey(getKey(initial));
40
+ }, [
41
+ collection.activeKey,
42
+ collection.enabledItems,
43
+ collection.setActiveKey,
44
+ getKey,
45
+ selectedKeys,
46
+ ]);
47
+ const setOpen = (next) => {
48
+ setKeyboardNavigation(false);
49
+ if (!next)
50
+ setQuery("");
51
+ if (controlledOpen === undefined)
52
+ setUncontrolledOpen(next);
53
+ onOpenChange?.(next);
54
+ };
55
+ const optionId = (key) => `${listboxId}-option-${encodeURIComponent(key)}`;
56
+ const commit = (item) => {
57
+ const next = getKey(item);
58
+ if (controlledKeys === undefined)
59
+ setUncontrolledValue(next);
60
+ onCommit(item);
61
+ if (trigger && !multiple)
62
+ setOpen(false);
63
+ };
64
+ const onInputChange = (event) => {
65
+ setKeyboardNavigation(true);
66
+ setQuery(event.target.value);
67
+ collection.setActiveKey(null);
68
+ };
69
+ const onInputKeyDown = (event) => {
70
+ if (isImeComposing(event))
71
+ return;
72
+ let move;
73
+ switch (event.key) {
74
+ case "ArrowDown":
75
+ move = [1, undefined];
76
+ break;
77
+ case "ArrowUp":
78
+ move = [-1, undefined];
79
+ break;
80
+ case "End":
81
+ move = [1, "end"];
82
+ break;
83
+ case "Home":
84
+ move = [-1, "start"];
85
+ break;
86
+ }
87
+ if (move) {
88
+ setKeyboardNavigation(true);
89
+ event.preventDefault();
90
+ collection.move(move[0], move[1]);
91
+ }
92
+ else if (event.key === "Enter" && collection.activeItem) {
93
+ event.preventDefault();
94
+ commit(collection.activeItem);
95
+ }
96
+ };
97
+ const groups = useMemo(() => {
98
+ if (!groupBy)
99
+ return [{ label: "", items: filtered }];
100
+ const grouped = new Map();
101
+ for (const item of filtered) {
102
+ const label = groupBy(item);
103
+ const group = grouped.get(label);
104
+ if (group)
105
+ group.push(item);
106
+ else
107
+ grouped.set(label, [item]);
108
+ }
109
+ return [...grouped].map(([label, groupItems]) => ({
110
+ label,
111
+ items: groupItems,
112
+ }));
113
+ }, [filtered, groupBy]);
114
+ return {
115
+ keyboardNavigation,
116
+ onPointerMove: () => {
117
+ setKeyboardNavigation(false);
118
+ },
119
+ activeKey: collection.activeKey,
120
+ activeItem: collection.activeItem,
121
+ groups,
122
+ inputValue: query,
123
+ listboxId,
124
+ onInputChange,
125
+ onInputKeyDown,
126
+ optionId,
127
+ open,
128
+ commit,
129
+ selectedKeys,
130
+ setOpen,
131
+ };
132
+ }
133
+ /** A searchable selection primitive with caller-owned values and intrinsic keyboard navigation. */
134
+ export function Combobox(props) {
135
+ const { "aria-describedby": describedBy, "aria-invalid": invalid, "aria-label": label, "aria-labelledby": labelledBy, "aria-required": required, activeTone = "neutral", container, contentClassName, defaultOpen = false, defaultValue = null, emptyMessage = "No matches", getKey, getLabel, getSearchText = getLabel, groupBy, id: providedId, inputProps, inputRef, isDisabled = () => false, items, onOpenChange, onValueChange, multiple, open: controlledOpen, placeholder = "Search…", renderItem, rootRef, trigger, triggerRef, value: controlledValue, ...rootProps } = props;
136
+ // Keep the public single/multiple callback types correlated at this boundary.
137
+ const onCommit = (item) => {
138
+ const key = getKey(item);
139
+ if (multiple) {
140
+ const next = controlledValue.includes(key)
141
+ ? controlledValue.filter((value) => value !== key)
142
+ : [...controlledValue, key];
143
+ onValueChange?.(next, items.filter((entry) => next.includes(getKey(entry))));
144
+ }
145
+ else {
146
+ onValueChange?.(key, item);
147
+ }
148
+ };
149
+ let controlledKeys;
150
+ if (multiple)
151
+ controlledKeys = controlledValue;
152
+ else if (controlledValue !== undefined)
153
+ controlledKeys = controlledValue === null ? [] : [controlledValue];
154
+ const generatedId = useId();
155
+ const controlProps = trigger?.props ?? inputProps;
156
+ const id = providedId ?? controlProps?.id ?? generatedId;
157
+ const selectionControlProps = {
158
+ id,
159
+ "aria-describedby": [describedBy, controlProps?.["aria-describedby"]]
160
+ .filter(Boolean)
161
+ .join(" ") || undefined,
162
+ "aria-invalid": invalid ?? controlProps?.["aria-invalid"],
163
+ "aria-label": controlProps?.["aria-label"] ?? label,
164
+ "aria-labelledby": controlProps?.["aria-labelledby"] ?? labelledBy,
165
+ "aria-required": required ?? controlProps?.["aria-required"],
166
+ };
167
+ const controller = useComboboxController({
168
+ defaultOpen,
169
+ defaultValue,
170
+ getKey,
171
+ getSearchText,
172
+ groupBy,
173
+ id,
174
+ isDisabled,
175
+ items,
176
+ onOpenChange,
177
+ onCommit,
178
+ multiple: multiple ?? false,
179
+ controlledOpen,
180
+ controlledKeys,
181
+ trigger,
182
+ });
183
+ const { keyboardNavigation, onPointerMove, activeItem, activeKey, commit, groups, inputValue, listboxId, onInputChange, onInputKeyDown, open, optionId, selectedKeys, setOpen, } = controller;
184
+ useOverlayDismiss(() => {
185
+ if (trigger)
186
+ setOpen(false);
187
+ });
188
+ const overlay = useOverlayRoot("popover");
189
+ const portalContainer = container === undefined ? overlay : container;
190
+ const listRef = useGlide('.caps-combobox-option[data-active="true"]:not(:disabled), [data-navigation="pointer"] .caps-combobox-option:hover:not(:disabled)');
191
+ const input = (_jsx(Input, { ...inputProps, ...(trigger ? {} : selectionControlProps), "aria-activedescendant": activeItem ? optionId(getKey(activeItem)) : undefined, "aria-autocomplete": "list", "aria-controls": listboxId, "aria-expanded": true, className: cn("input-sm", inputProps?.className), id: trigger ? (inputProps?.id ?? `${id}-input`) : id, onChange: (event) => {
192
+ inputProps?.onChange?.(event);
193
+ onInputChange(event);
194
+ }, onKeyDown: (event) => {
195
+ inputProps?.onKeyDown?.(event);
196
+ if (!event.defaultPrevented)
197
+ onInputKeyDown(event);
198
+ }, placeholder: inputProps?.placeholder ?? placeholder, ref: inputRef, role: "combobox", value: inputValue }));
199
+ const list = (_jsx("div", { id: listboxId, ref: listRef, className: "caps-combobox-list", "data-navigation": keyboardNavigation ? "keyboard" : "pointer", onPointerMove: onPointerMove, role: "listbox", "aria-multiselectable": multiple ?? undefined, children: groups.every((group) => group.items.length === 0) ? (_jsx("div", { className: "px-md py-md text-sm text-base-content", children: emptyMessage })) : (groups.map((group) => (_jsxs("div", { "aria-label": group.label || undefined, role: group.label ? "group" : undefined, children: [group.label ? (_jsx("div", { className: "px-md pb-xs pt-sm text-xs text-base-content uppercase tracking-wider", children: group.label })) : null, group.items.map((item) => {
200
+ const key = getKey(item);
201
+ const disabled = isDisabled(item);
202
+ const active = activeKey === key;
203
+ const state = {
204
+ active,
205
+ disabled,
206
+ selected: selectedKeys.includes(key),
207
+ };
208
+ return (_jsxs("button", { "aria-disabled": disabled || undefined, "aria-selected": state.selected, className: "caps-combobox-option", "data-active": active && keyboardNavigation, disabled: disabled, id: optionId(key), onClick: () => {
209
+ commit(item);
210
+ }, onMouseDown: (event) => {
211
+ // Keep typing in the search field after a pointer selection.
212
+ event.preventDefault();
213
+ }, role: "option", tabIndex: -1, type: "button", children: [_jsx("span", { "aria-hidden": "true", className: "caps-combobox-check", children: state.selected ? _jsx(Check, {}) : null }), renderItem?.(item, active, state) ?? getLabel(item)] }, key));
214
+ })] }, group.label)))) }));
215
+ if (!trigger)
216
+ return (_jsx("div", { ...rootProps, className: cn(rootProps.className), ref: rootRef, children: _jsxs("div", { className: cn("caps-combobox-panel", contentClassName), "data-active-tone": activeTone, children: [_jsx("div", { className: "caps-combobox-search", children: input }), list] }) }));
217
+ return (_jsx("div", { ...rootProps, className: cn(rootProps.className), ref: rootRef, children: _jsxs(BasePopover.Root, { modal: true, onOpenChange: setOpen, open: open, children: [_jsx(BasePopover.Trigger, { ...selectionControlProps, "aria-haspopup": "dialog", className: "btn", ref: triggerRef, render: trigger }), portalContainer !== null ? (_jsx(BasePopover.Portal, { container: portalContainer, children: _jsx(BasePopover.Positioner, { align: "start", side: "bottom", sideOffset: 4, children: _jsxs(BasePopover.Popup, { "data-active-tone": activeTone, className: cn("min-w-40 rounded-box border border-base-300 bg-base-100 p-xs shadow-lg outline-none caps-combobox-panel caps-combobox-popup", contentClassName), children: [_jsx("div", { className: "caps-combobox-search", children: input }), list] }) }) })) : null] }) }));
218
+ }
@@ -0,0 +1,23 @@
1
+ /** A deliberate hold, or an exact phrase before a destructive action. */
2
+ export type ConfirmActionProps = {
3
+ /** The action's trigger label and dialog title. */
4
+ label: string;
5
+ /** The destructive key's label (and the hold gesture's instruction). */
6
+ confirmLabel: string;
7
+ /** Resolve on success; reject on failure. The component prevents concurrent calls. */
8
+ onConfirm: () => Promise<void>;
9
+ /** Caller-owned, safe-to-display failure copy; rejection details are never displayed. */
10
+ error?: string;
11
+ } & ({
12
+ mode: "hold";
13
+ phrase?: never;
14
+ } | {
15
+ mode: "type";
16
+ phrase: string;
17
+ });
18
+ /**
19
+ * Confirms once per gesture, owns pending/result feedback, and keeps failed
20
+ * confirmations available for retry. Type mode closes on success and restores
21
+ * focus through Dialog. A pending action cannot be dismissed or cancelled.
22
+ */
23
+ export declare function ConfirmAction({ label, confirmLabel, onConfirm, error, mode, phrase, }: ConfirmActionProps): import("react/jsx-runtime").JSX.Element;