@webjsdev/ui 0.3.17 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/README.md +66 -7
  2. package/THIRD_PARTY_NOTICES.md +349 -0
  3. package/package.json +4 -2
  4. package/packages/registry/blocks/_shared/placeholder.svg +1 -0
  5. package/packages/registry/blocks/charts/chart-area-axes.ts +72 -0
  6. package/packages/registry/blocks/charts/chart-area-default.ts +64 -0
  7. package/packages/registry/blocks/charts/chart-area-gradient.ts +82 -0
  8. package/packages/registry/blocks/charts/chart-area-icons.ts +77 -0
  9. package/packages/registry/blocks/charts/chart-area-interactive.ts +204 -0
  10. package/packages/registry/blocks/charts/chart-area-legend.ts +72 -0
  11. package/packages/registry/blocks/charts/chart-area-linear.ts +64 -0
  12. package/packages/registry/blocks/charts/chart-area-stacked-expand.ts +77 -0
  13. package/packages/registry/blocks/charts/chart-area-stacked.ts +71 -0
  14. package/packages/registry/blocks/charts/chart-area-step.ts +68 -0
  15. package/packages/registry/blocks/charts/chart-bar-active.ts +93 -0
  16. package/packages/registry/blocks/charts/chart-bar-default.ts +60 -0
  17. package/packages/registry/blocks/charts/chart-bar-horizontal.ts +62 -0
  18. package/packages/registry/blocks/charts/chart-bar-interactive.ts +185 -0
  19. package/packages/registry/blocks/charts/chart-bar-label-custom.ts +78 -0
  20. package/packages/registry/blocks/charts/chart-bar-label.ts +66 -0
  21. package/packages/registry/blocks/charts/chart-bar-mixed.ts +87 -0
  22. package/packages/registry/blocks/charts/chart-bar-multiple.ts +67 -0
  23. package/packages/registry/blocks/charts/chart-bar-negative.ts +61 -0
  24. package/packages/registry/blocks/charts/chart-bar-stacked.ts +68 -0
  25. package/packages/registry/blocks/charts/chart-line-default.ts +61 -0
  26. package/packages/registry/blocks/charts/chart-line-dots-colors.ts +86 -0
  27. package/packages/registry/blocks/charts/chart-line-dots-custom.ts +75 -0
  28. package/packages/registry/blocks/charts/chart-line-dots.ts +65 -0
  29. package/packages/registry/blocks/charts/chart-line-interactive.ts +185 -0
  30. package/packages/registry/blocks/charts/chart-line-label-custom.ts +94 -0
  31. package/packages/registry/blocks/charts/chart-line-label.ts +73 -0
  32. package/packages/registry/blocks/charts/chart-line-linear.ts +61 -0
  33. package/packages/registry/blocks/charts/chart-line-multiple.ts +71 -0
  34. package/packages/registry/blocks/charts/chart-line-step.ts +61 -0
  35. package/packages/registry/blocks/charts/chart-pie-donut-active.ts +86 -0
  36. package/packages/registry/blocks/charts/chart-pie-donut-text.ts +89 -0
  37. package/packages/registry/blocks/charts/chart-pie-donut.ts +77 -0
  38. package/packages/registry/blocks/charts/chart-pie-interactive.ts +120 -0
  39. package/packages/registry/blocks/charts/chart-pie-label-custom.ts +82 -0
  40. package/packages/registry/blocks/charts/chart-pie-label-list.ts +86 -0
  41. package/packages/registry/blocks/charts/chart-pie-label.ts +77 -0
  42. package/packages/registry/blocks/charts/chart-pie-legend.ts +79 -0
  43. package/packages/registry/blocks/charts/chart-pie-separator-none.ts +77 -0
  44. package/packages/registry/blocks/charts/chart-pie-simple.ts +77 -0
  45. package/packages/registry/blocks/charts/chart-pie-stacked.ts +99 -0
  46. package/packages/registry/blocks/charts/chart-radar-default.ts +61 -0
  47. package/packages/registry/blocks/charts/chart-radar-dots.ts +61 -0
  48. package/packages/registry/blocks/charts/chart-radar-grid-circle-fill.ts +61 -0
  49. package/packages/registry/blocks/charts/chart-radar-grid-circle-no-lines.ts +61 -0
  50. package/packages/registry/blocks/charts/chart-radar-grid-circle.ts +61 -0
  51. package/packages/registry/blocks/charts/chart-radar-grid-custom.ts +61 -0
  52. package/packages/registry/blocks/charts/chart-radar-grid-fill.ts +61 -0
  53. package/packages/registry/blocks/charts/chart-radar-grid-none.ts +60 -0
  54. package/packages/registry/blocks/charts/chart-radar-icons.ts +76 -0
  55. package/packages/registry/blocks/charts/chart-radar-label-custom.ts +77 -0
  56. package/packages/registry/blocks/charts/chart-radar-legend.ts +70 -0
  57. package/packages/registry/blocks/charts/chart-radar-lines-only.ts +68 -0
  58. package/packages/registry/blocks/charts/chart-radar-multiple.ts +68 -0
  59. package/packages/registry/blocks/charts/chart-radar-radius.ts +68 -0
  60. package/packages/registry/blocks/charts/chart-radial-grid.ts +80 -0
  61. package/packages/registry/blocks/charts/chart-radial-label.ts +85 -0
  62. package/packages/registry/blocks/charts/chart-radial-shape.ts +81 -0
  63. package/packages/registry/blocks/charts/chart-radial-simple.ts +79 -0
  64. package/packages/registry/blocks/charts/chart-radial-stacked.ts +69 -0
  65. package/packages/registry/blocks/charts/chart-radial-text.ts +82 -0
  66. package/packages/registry/blocks/charts/chart-tooltip-advanced.ts +84 -0
  67. package/packages/registry/blocks/charts/chart-tooltip-default.ts +62 -0
  68. package/packages/registry/blocks/charts/chart-tooltip-formatter.ts +75 -0
  69. package/packages/registry/blocks/charts/chart-tooltip-icons.ts +68 -0
  70. package/packages/registry/blocks/charts/chart-tooltip-indicator-line.ts +62 -0
  71. package/packages/registry/blocks/charts/chart-tooltip-indicator-none.ts +62 -0
  72. package/packages/registry/blocks/charts/chart-tooltip-label-custom.ts +65 -0
  73. package/packages/registry/blocks/charts/chart-tooltip-label-formatter.ts +66 -0
  74. package/packages/registry/blocks/charts/chart-tooltip-label-none.ts +62 -0
  75. package/packages/registry/blocks/dashboard-01/components/app-sidebar.ts +108 -0
  76. package/packages/registry/blocks/dashboard-01/components/chart-area-interactive.ts +276 -0
  77. package/packages/registry/blocks/dashboard-01/components/dashboard-data.ts +89 -0
  78. package/packages/registry/blocks/dashboard-01/components/data-table.ts +637 -0
  79. package/packages/registry/blocks/dashboard-01/components/nav-documents.ts +65 -0
  80. package/packages/registry/blocks/dashboard-01/components/nav-main.ts +61 -0
  81. package/packages/registry/blocks/dashboard-01/components/nav-secondary.ts +40 -0
  82. package/packages/registry/blocks/dashboard-01/components/nav-user.ts +66 -0
  83. package/packages/registry/blocks/dashboard-01/components/section-cards.ts +59 -0
  84. package/packages/registry/blocks/dashboard-01/components/site-header.ts +52 -0
  85. package/packages/registry/blocks/dashboard-01/page.ts +39 -0
  86. package/packages/registry/blocks/login-01/components/login-form.ts +70 -0
  87. package/packages/registry/blocks/login-01/page.ts +19 -0
  88. package/packages/registry/blocks/login-02/components/login-form.ts +68 -0
  89. package/packages/registry/blocks/login-02/page.ts +36 -0
  90. package/packages/registry/blocks/login-03/components/login-form.ts +89 -0
  91. package/packages/registry/blocks/login-03/page.ts +28 -0
  92. package/packages/registry/blocks/login-04/components/login-form.ts +91 -0
  93. package/packages/registry/blocks/login-04/page.ts +20 -0
  94. package/packages/registry/blocks/login-05/components/login-form.ts +75 -0
  95. package/packages/registry/blocks/login-05/page.ts +19 -0
  96. package/packages/registry/blocks/preview/cards/activate-agent-dialog.ts +77 -0
  97. package/packages/registry/blocks/preview/cards/analytics-card.ts +64 -0
  98. package/packages/registry/blocks/preview/cards/anomaly-alert.ts +35 -0
  99. package/packages/registry/blocks/preview/cards/bar-chart-card.ts +91 -0
  100. package/packages/registry/blocks/preview/cards/book-appointment.ts +51 -0
  101. package/packages/registry/blocks/preview/cards/codespaces-card.ts +172 -0
  102. package/packages/registry/blocks/preview/cards/contributions-activity.ts +63 -0
  103. package/packages/registry/blocks/preview/cards/contributors.ts +51 -0
  104. package/packages/registry/blocks/preview/cards/environment-variables.ts +47 -0
  105. package/packages/registry/blocks/preview/cards/feedback-form.ts +61 -0
  106. package/packages/registry/blocks/preview/cards/file-upload.ts +51 -0
  107. package/packages/registry/blocks/preview/cards/github-profile.ts +66 -0
  108. package/packages/registry/blocks/preview/cards/icon-preview-grid.ts +47 -0
  109. package/packages/registry/blocks/preview/cards/invite-team.ts +78 -0
  110. package/packages/registry/blocks/preview/cards/invoice.ts +91 -0
  111. package/packages/registry/blocks/preview/cards/live-waveform.ts +330 -0
  112. package/packages/registry/blocks/preview/cards/no-team-members.ts +61 -0
  113. package/packages/registry/blocks/preview/cards/not-found.ts +46 -0
  114. package/packages/registry/blocks/preview/cards/observability-card.ts +44 -0
  115. package/packages/registry/blocks/preview/cards/pie-chart-card.ts +84 -0
  116. package/packages/registry/blocks/preview/cards/report-bug.ts +71 -0
  117. package/packages/registry/blocks/preview/cards/shipping-address.ts +79 -0
  118. package/packages/registry/blocks/preview/cards/shortcuts.ts +57 -0
  119. package/packages/registry/blocks/preview/cards/skeleton-loading.ts +37 -0
  120. package/packages/registry/blocks/preview/cards/sleep-report.ts +85 -0
  121. package/packages/registry/blocks/preview/cards/style-overview.ts +64 -0
  122. package/packages/registry/blocks/preview/cards/typography-specimen.ts +107 -0
  123. package/packages/registry/blocks/preview/cards/ui-elements.ts +164 -0
  124. package/packages/registry/blocks/preview/cards/usage-card.ts +63 -0
  125. package/packages/registry/blocks/preview/cards/visitors.ts +71 -0
  126. package/packages/registry/blocks/preview/cards/weekly-fitness-summary.ts +54 -0
  127. package/packages/registry/blocks/preview/page.ts +103 -0
  128. package/packages/registry/blocks/preview-02/cards/account-access.ts +70 -0
  129. package/packages/registry/blocks/preview-02/cards/card-overview.ts +75 -0
  130. package/packages/registry/blocks/preview-02/cards/claimable-balance.ts +54 -0
  131. package/packages/registry/blocks/preview-02/cards/contribution-history.ts +80 -0
  132. package/packages/registry/blocks/preview-02/cards/cover-art.ts +41 -0
  133. package/packages/registry/blocks/preview-02/cards/dividend-income.ts +76 -0
  134. package/packages/registry/blocks/preview-02/cards/empty-connect-bank.ts +38 -0
  135. package/packages/registry/blocks/preview-02/cards/empty-distribute-track.ts +38 -0
  136. package/packages/registry/blocks/preview-02/cards/empty-explore-catalog.ts +39 -0
  137. package/packages/registry/blocks/preview-02/cards/faq.ts +79 -0
  138. package/packages/registry/blocks/preview-02/cards/front-door.ts +40 -0
  139. package/packages/registry/blocks/preview-02/cards/index-investing.ts +31 -0
  140. package/packages/registry/blocks/preview-02/cards/kitchen-island.ts +90 -0
  141. package/packages/registry/blocks/preview-02/cards/loading-card.ts +34 -0
  142. package/packages/registry/blocks/preview-02/cards/new-milestone.ts +53 -0
  143. package/packages/registry/blocks/preview-02/cards/notification-settings.ts +126 -0
  144. package/packages/registry/blocks/preview-02/cards/payments.ts +88 -0
  145. package/packages/registry/blocks/preview-02/cards/payout-threshold.ts +92 -0
  146. package/packages/registry/blocks/preview-02/cards/power-usage.ts +84 -0
  147. package/packages/registry/blocks/preview-02/cards/preferences.ts +89 -0
  148. package/packages/registry/blocks/preview-02/cards/qr-connect.ts +54 -0
  149. package/packages/registry/blocks/preview-02/cards/receiving-method.ts +89 -0
  150. package/packages/registry/blocks/preview-02/cards/recent-transactions.ts +101 -0
  151. package/packages/registry/blocks/preview-02/cards/release-catalog.ts +73 -0
  152. package/packages/registry/blocks/preview-02/cards/roller-shades.ts +110 -0
  153. package/packages/registry/blocks/preview-02/cards/savings-progress.ts +68 -0
  154. package/packages/registry/blocks/preview-02/cards/savings-targets.ts +132 -0
  155. package/packages/registry/blocks/preview-02/cards/sidebar-nav.ts +88 -0
  156. package/packages/registry/blocks/preview-02/cards/social-links.ts +57 -0
  157. package/packages/registry/blocks/preview-02/cards/stock-performance.ts +106 -0
  158. package/packages/registry/blocks/preview-02/cards/syncing-state.ts +37 -0
  159. package/packages/registry/blocks/preview-02/cards/transfer-funds.ts +111 -0
  160. package/packages/registry/blocks/preview-02/cards/upcoming-payments.ts +69 -0
  161. package/packages/registry/blocks/preview-02/page.ts +111 -0
  162. package/packages/registry/blocks/preview-03/cards/files-chat.ts +75 -0
  163. package/packages/registry/blocks/preview-03/cards/group-chat.ts +114 -0
  164. package/packages/registry/blocks/preview-03/cards/reasoning-chat.ts +88 -0
  165. package/packages/registry/blocks/preview-03/cards/simple-chat.ts +53 -0
  166. package/packages/registry/blocks/preview-03/components/chat-card.ts +126 -0
  167. package/packages/registry/blocks/preview-03/components/chat-player.ts +174 -0
  168. package/packages/registry/blocks/preview-03/components/part-file.ts +77 -0
  169. package/packages/registry/blocks/preview-03/components/part-reasoning.ts +25 -0
  170. package/packages/registry/blocks/preview-03/components/part-source.ts +48 -0
  171. package/packages/registry/blocks/preview-03/components/part-text.ts +69 -0
  172. package/packages/registry/blocks/preview-03/components/part-tool.ts +71 -0
  173. package/packages/registry/blocks/preview-03/components/prompt-input.ts +86 -0
  174. package/packages/registry/blocks/preview-03/page.ts +39 -0
  175. package/packages/registry/blocks/sidebar-01/components/app-sidebar.ts +113 -0
  176. package/packages/registry/blocks/sidebar-01/components/search-form.ts +33 -0
  177. package/packages/registry/blocks/sidebar-01/components/version-switcher.ts +50 -0
  178. package/packages/registry/blocks/sidebar-01/page.ts +58 -0
  179. package/packages/registry/blocks/sidebar-02/components/app-sidebar.ts +131 -0
  180. package/packages/registry/blocks/sidebar-02/components/search-form.ts +33 -0
  181. package/packages/registry/blocks/sidebar-02/components/version-switcher.ts +50 -0
  182. package/packages/registry/blocks/sidebar-02/page.ts +53 -0
  183. package/packages/registry/blocks/sidebar-03/components/app-sidebar.ts +134 -0
  184. package/packages/registry/blocks/sidebar-03/page.ts +60 -0
  185. package/packages/registry/blocks/sidebar-04/components/app-sidebar.ts +136 -0
  186. package/packages/registry/blocks/sidebar-04/page.ts +58 -0
  187. package/packages/registry/blocks/sidebar-05/components/app-sidebar.ts +147 -0
  188. package/packages/registry/blocks/sidebar-05/components/search-form.ts +33 -0
  189. package/packages/registry/blocks/sidebar-05/page.ts +58 -0
  190. package/packages/registry/blocks/sidebar-06/components/app-sidebar.ts +105 -0
  191. package/packages/registry/blocks/sidebar-06/components/nav-main.ts +56 -0
  192. package/packages/registry/blocks/sidebar-06/components/sidebar-opt-in-form.ts +44 -0
  193. package/packages/registry/blocks/sidebar-06/page.ts +57 -0
  194. package/packages/registry/blocks/sidebar-07/components/app-sidebar.ts +108 -0
  195. package/packages/registry/blocks/sidebar-07/components/nav-main.ts +61 -0
  196. package/packages/registry/blocks/sidebar-07/components/nav-projects.ts +65 -0
  197. package/packages/registry/blocks/sidebar-07/components/nav-user.ts +70 -0
  198. package/packages/registry/blocks/sidebar-07/components/team-switcher.ts +66 -0
  199. package/packages/registry/blocks/sidebar-07/page.ts +59 -0
  200. package/packages/registry/blocks/sidebar-08/components/app-sidebar.ts +122 -0
  201. package/packages/registry/blocks/sidebar-08/components/nav-main.ts +68 -0
  202. package/packages/registry/blocks/sidebar-08/components/nav-projects.ts +65 -0
  203. package/packages/registry/blocks/sidebar-08/components/nav-secondary.ts +40 -0
  204. package/packages/registry/blocks/sidebar-08/components/nav-user.ts +70 -0
  205. package/packages/registry/blocks/sidebar-08/page.ts +59 -0
  206. package/packages/registry/blocks/sidebar-09/components/app-sidebar.ts +213 -0
  207. package/packages/registry/blocks/sidebar-09/components/nav-user.ts +70 -0
  208. package/packages/registry/blocks/sidebar-09/page.ts +52 -0
  209. package/packages/registry/blocks/sidebar-10/components/app-sidebar.ts +125 -0
  210. package/packages/registry/blocks/sidebar-10/components/nav-actions.ts +84 -0
  211. package/packages/registry/blocks/sidebar-10/components/nav-favorites.ts +66 -0
  212. package/packages/registry/blocks/sidebar-10/components/nav-main.ts +37 -0
  213. package/packages/registry/blocks/sidebar-10/components/nav-secondary.ts +43 -0
  214. package/packages/registry/blocks/sidebar-10/components/nav-workspaces.ts +78 -0
  215. package/packages/registry/blocks/sidebar-10/components/team-switcher.ts +63 -0
  216. package/packages/registry/blocks/sidebar-10/page.ts +46 -0
  217. package/packages/registry/blocks/sidebar-11/components/app-sidebar.ts +114 -0
  218. package/packages/registry/blocks/sidebar-11/page.ts +63 -0
  219. package/packages/registry/blocks/sidebar-12/components/app-sidebar.ts +63 -0
  220. package/packages/registry/blocks/sidebar-12/components/calendars.ts +53 -0
  221. package/packages/registry/blocks/sidebar-12/components/date-picker.ts +26 -0
  222. package/packages/registry/blocks/sidebar-12/components/nav-user.ts +70 -0
  223. package/packages/registry/blocks/sidebar-12/page.ts +43 -0
  224. package/packages/registry/blocks/sidebar-13/components/settings-dialog.ts +114 -0
  225. package/packages/registry/blocks/sidebar-13/page.ts +21 -0
  226. package/packages/registry/blocks/sidebar-14/components/app-sidebar.ts +120 -0
  227. package/packages/registry/blocks/sidebar-14/page.ts +55 -0
  228. package/packages/registry/blocks/sidebar-15/components/calendars.ts +53 -0
  229. package/packages/registry/blocks/sidebar-15/components/date-picker.ts +26 -0
  230. package/packages/registry/blocks/sidebar-15/components/nav-favorites.ts +66 -0
  231. package/packages/registry/blocks/sidebar-15/components/nav-main.ts +35 -0
  232. package/packages/registry/blocks/sidebar-15/components/nav-secondary.ts +43 -0
  233. package/packages/registry/blocks/sidebar-15/components/nav-user.ts +70 -0
  234. package/packages/registry/blocks/sidebar-15/components/nav-workspaces.ts +75 -0
  235. package/packages/registry/blocks/sidebar-15/components/sidebar-left.ts +126 -0
  236. package/packages/registry/blocks/sidebar-15/components/sidebar-right.ts +62 -0
  237. package/packages/registry/blocks/sidebar-15/components/team-switcher.ts +63 -0
  238. package/packages/registry/blocks/sidebar-15/page.ts +47 -0
  239. package/packages/registry/blocks/sidebar-16/components/app-sidebar.ts +124 -0
  240. package/packages/registry/blocks/sidebar-16/components/nav-main.ts +67 -0
  241. package/packages/registry/blocks/sidebar-16/components/nav-projects.ts +65 -0
  242. package/packages/registry/blocks/sidebar-16/components/nav-secondary.ts +40 -0
  243. package/packages/registry/blocks/sidebar-16/components/nav-user.ts +70 -0
  244. package/packages/registry/blocks/sidebar-16/components/search-form.ts +27 -0
  245. package/packages/registry/blocks/sidebar-16/components/site-header.ts +42 -0
  246. package/packages/registry/blocks/sidebar-16/page.ts +38 -0
  247. package/packages/registry/blocks/signup-01/components/signup-form.ts +78 -0
  248. package/packages/registry/blocks/signup-01/page.ts +19 -0
  249. package/packages/registry/blocks/signup-02/components/signup-form.ts +78 -0
  250. package/packages/registry/blocks/signup-02/page.ts +36 -0
  251. package/packages/registry/blocks/signup-03/components/signup-form.ts +79 -0
  252. package/packages/registry/blocks/signup-03/page.ts +28 -0
  253. package/packages/registry/blocks/signup-04/components/signup-form.ts +100 -0
  254. package/packages/registry/blocks/signup-04/page.ts +20 -0
  255. package/packages/registry/blocks/signup-05/components/signup-form.ts +75 -0
  256. package/packages/registry/blocks/signup-05/page.ts +20 -0
  257. package/packages/registry/components/accordion.ts +20 -7
  258. package/packages/registry/components/alert-dialog.ts +58 -13
  259. package/packages/registry/components/alert.ts +11 -6
  260. package/packages/registry/components/attachment.ts +119 -0
  261. package/packages/registry/components/avatar.ts +17 -8
  262. package/packages/registry/components/badge.ts +16 -9
  263. package/packages/registry/components/breadcrumb.ts +6 -6
  264. package/packages/registry/components/bubble.ts +95 -0
  265. package/packages/registry/components/button-group.ts +73 -0
  266. package/packages/registry/components/button.ts +35 -25
  267. package/packages/registry/components/calendar.ts +566 -0
  268. package/packages/registry/components/card.ts +8 -8
  269. package/packages/registry/components/carousel.ts +352 -0
  270. package/packages/registry/components/chart-element.ts +547 -0
  271. package/packages/registry/components/chart-geometry.ts +457 -0
  272. package/packages/registry/components/chart.ts +932 -0
  273. package/packages/registry/components/checkbox.ts +22 -84
  274. package/packages/registry/components/code-block.ts +191 -0
  275. package/packages/registry/components/collapsible.ts +5 -2
  276. package/packages/registry/components/combobox.ts +802 -0
  277. package/packages/registry/components/command.ts +618 -0
  278. package/packages/registry/components/context-menu.ts +302 -0
  279. package/packages/registry/components/dialog.ts +156 -45
  280. package/packages/registry/components/direction.ts +43 -0
  281. package/packages/registry/components/drawer.ts +226 -0
  282. package/packages/registry/components/dropdown-menu.ts +262 -83
  283. package/packages/registry/components/empty.ts +8 -8
  284. package/packages/registry/components/field.ts +20 -18
  285. package/packages/registry/components/floating.ts +124 -0
  286. package/packages/registry/components/hover-card.ts +19 -8
  287. package/packages/registry/components/input-group.ts +118 -0
  288. package/packages/registry/components/input-otp.ts +365 -0
  289. package/packages/registry/components/input.ts +3 -3
  290. package/packages/registry/components/item.ts +134 -0
  291. package/packages/registry/components/kbd.ts +2 -2
  292. package/packages/registry/components/label.ts +1 -1
  293. package/packages/registry/components/marker.ts +63 -0
  294. package/packages/registry/components/menubar.ts +388 -0
  295. package/packages/registry/components/message-scroller.ts +470 -0
  296. package/packages/registry/components/message.ts +69 -0
  297. package/packages/registry/components/native-select.ts +5 -5
  298. package/packages/registry/components/navigation-menu.ts +564 -0
  299. package/packages/registry/components/pagination.ts +5 -5
  300. package/packages/registry/components/popover.ts +30 -84
  301. package/packages/registry/components/progress.ts +7 -3
  302. package/packages/registry/components/questionnaire.ts +840 -0
  303. package/packages/registry/components/radio-group.ts +18 -73
  304. package/packages/registry/components/resizable.ts +330 -0
  305. package/packages/registry/components/scroll-area.ts +46 -0
  306. package/packages/registry/components/select.ts +650 -0
  307. package/packages/registry/components/separator.ts +1 -1
  308. package/packages/registry/components/sheet.ts +128 -0
  309. package/packages/registry/components/sidebar.ts +457 -0
  310. package/packages/registry/components/skeleton.ts +1 -1
  311. package/packages/registry/components/slider.ts +333 -0
  312. package/packages/registry/components/sonner.ts +74 -7
  313. package/packages/registry/components/spinner.ts +7 -4
  314. package/packages/registry/components/switch.ts +117 -9
  315. package/packages/registry/components/table.ts +11 -9
  316. package/packages/registry/components/tabs.ts +76 -25
  317. package/packages/registry/components/textarea.ts +1 -1
  318. package/packages/registry/components/toggle-group.ts +30 -11
  319. package/packages/registry/components/toggle.ts +20 -13
  320. package/packages/registry/components/tooltip.ts +68 -7
  321. package/packages/registry/components/trace.ts +75 -0
  322. package/packages/registry/lib/icons-hugeicons.ts +955 -0
  323. package/packages/registry/lib/icons-phosphor.ts +955 -0
  324. package/packages/registry/lib/icons-remixicon.ts +955 -0
  325. package/packages/registry/lib/icons-tabler.ts +955 -0
  326. package/packages/registry/lib/icons.ts +768 -0
  327. package/packages/registry/lib/utils.ts +49 -4
  328. package/packages/registry/registry.json +4056 -72
  329. package/packages/registry/styles/shadcn-tailwind.css +654 -0
  330. package/packages/registry/styles/style-luma.css +1987 -0
  331. package/packages/registry/styles/style-lyra.css +1965 -0
  332. package/packages/registry/styles/style-maia.css +1987 -0
  333. package/packages/registry/styles/style-mira.css +1992 -0
  334. package/packages/registry/styles/style-new-york.css +1597 -0
  335. package/packages/registry/styles/style-nova.css +1990 -0
  336. package/packages/registry/styles/style-rhea.css +1987 -0
  337. package/packages/registry/styles/style-sera.css +1977 -0
  338. package/packages/registry/styles/style-vega.css +1990 -0
  339. package/packages/registry/styles/supplement.json +54 -0
  340. package/packages/registry/styles/tw-animate.css +6 -0
  341. package/packages/registry/themes/base-colors.js +8 -2
  342. package/packages/registry/themes/fonts.js +120 -0
  343. package/packages/registry/themes/index.css +2 -0
  344. package/packages/registry/themes/items.js +61 -0
  345. package/packages/registry/themes/preset.js +302 -0
  346. package/packages/registry/themes/themes.js +1193 -0
  347. package/src/commands/add.js +236 -12
  348. package/src/commands/diff.js +34 -20
  349. package/src/commands/info.js +7 -0
  350. package/src/commands/init.js +125 -8
  351. package/src/commands/list.js +43 -11
  352. package/src/commands/view.js +33 -5
  353. package/src/registry/extract.js +192 -2
  354. package/src/registry/icons.js +31 -0
  355. package/src/registry/local.js +15 -1
  356. package/src/registry/schema.js +13 -0
  357. package/src/registry/style-bake.js +506 -0
  358. package/src/utils/theme.js +110 -21
@@ -0,0 +1,840 @@
1
+ /**
2
+ * Questionnaire: a multi-step flow of questions over real form controls.
3
+ * Tier-2. It is shadcn's Questionnaire (the headless primitive in
4
+ * `@shadcn/react`, styled), built on a native `<form>`: each question is a
5
+ * `<fieldset>`, each answer a real radio, checkbox or text input, and the
6
+ * answers are read with `FormData` like any form.
7
+ *
8
+ * Without JavaScript every question shows at once and Submit posts the form,
9
+ * with the browser's own `required` check. With JavaScript one question shows
10
+ * at a time: Next validates it, Previous goes back, Skip leaves an optional
11
+ * question unanswered, and Submit appears on the last one.
12
+ *
13
+ * shadcn parity:
14
+ * Questionnaire (shortcuts) → <ui-questionnaire shortcuts="letters" | "numbers" action method>
15
+ * Questionnaire item / defaultItem
16
+ * → the `item` property and attribute (the active question,
17
+ * settable from outside) / `default-item`
18
+ * Questionnaire render={<form action={…} />}
19
+ * → <ui-questionnaire as-child> around your own <form>
20
+ * QuestionnaireProgress → <ui-questionnaire-progress> ("Question 1 of 3")
21
+ * QuestionnaireItem (name, required, multiple, disabled)
22
+ * → <ui-questionnaire-item name required multiple disabled>
23
+ * QuestionnaireTitle / Description
24
+ * → <ui-questionnaire-title> / <ui-questionnaire-description>
25
+ * QuestionnaireChoices → <ui-questionnaire-choices>
26
+ * QuestionnaireChoice value → <ui-questionnaire-choice value checked>
27
+ * QuestionnaireChoiceDescription
28
+ * → <ui-questionnaire-choice-description>
29
+ * QuestionnaireInput → <ui-questionnaire-input placeholder aria-label> (a freeform answer)
30
+ * QuestionnaireError → <ui-questionnaire-error> (a default message when empty)
31
+ * QuestionnaireActions / Previous / Skip / Next / Submit (disabled)
32
+ * → <ui-questionnaire-actions> / -previous / -skip / -next / -submit
33
+ * (`variant` and `size` as on a button, and `disabled`)
34
+ * onActiveItemChange → `ui-active-change` ({ detail: { name } })
35
+ *
36
+ * A question's answers share its `name`, so `FormData.get(name)` reads a
37
+ * single answer and `getAll(name)` a `multiple` one. Typing a freeform answer
38
+ * clears a single question's choices, and picking a choice clears the text.
39
+ * A `disabled` question is left out of the flow (a conditional question).
40
+ *
41
+ * The active question is the `item` property (and attribute): set it from
42
+ * outside to go to that question, and it follows the reader's own steps.
43
+ * `default-item` names the question the flow opens on. A navigation button
44
+ * written `disabled` cannot be pressed, and a disabled Next or Submit also
45
+ * stops Enter from stepping on, as in shadcn.
46
+ *
47
+ * To bind a `'use server'` action, write the form yourself, the way a page
48
+ * binds any action, and mark the questionnaire `as-child`:
49
+ * `<ui-questionnaire as-child><form action=${saveAnswers} data-slot="questionnaire"
50
+ * class=${questionnaireClass()}> ...questions and actions... </form></ui-questionnaire>`.
51
+ * The element then renders no form of its own and drives yours, so the page's
52
+ * binding (its hidden action field, `method="post"`) is served and kept, and
53
+ * the form posts with JavaScript off. Without `as-child` the element renders
54
+ * the form, with your `action` and `method` attributes as plain strings.
55
+ *
56
+ * Keyboard: Enter goes to the next question, and on the last one submits.
57
+ * With `shortcuts`, a letter (A, B, C) or a number (1, 2, 3) picks the
58
+ * matching answer, except while typing in a text field.
59
+ *
60
+ * A11y (handled by the element): each question is a `<fieldset>` named by its
61
+ * title and described by its description, the progress is a
62
+ * `role="progressbar"` with its value, a question left unanswered is marked
63
+ * `aria-invalid` and its error announced with `role="alert"`, and a question
64
+ * not on screen is `hidden` and `inert`.
65
+ *
66
+ * @example
67
+ * ```html
68
+ * <ui-questionnaire shortcuts="letters">
69
+ * <ui-questionnaire-progress></ui-questionnaire-progress>
70
+ * <ui-questionnaire-item name="direction" required>
71
+ * <ui-questionnaire-title>What should we prototype next?</ui-questionnaire-title>
72
+ * <ui-questionnaire-description>Choose a direction or write your own.</ui-questionnaire-description>
73
+ * <ui-questionnaire-choices>
74
+ * <ui-questionnaire-choice value="delegation">Delegation</ui-questionnaire-choice>
75
+ * <ui-questionnaire-choice value="questions">Question prompts</ui-questionnaire-choice>
76
+ * <ui-questionnaire-input aria-label="Another answer" placeholder="Type another answer"></ui-questionnaire-input>
77
+ * </ui-questionnaire-choices>
78
+ * <ui-questionnaire-error></ui-questionnaire-error>
79
+ * </ui-questionnaire-item>
80
+ * <ui-questionnaire-item name="detail">
81
+ * <ui-questionnaire-title>How much detail?</ui-questionnaire-title>
82
+ * <ui-questionnaire-choices>
83
+ * <ui-questionnaire-choice value="focused">Focused</ui-questionnaire-choice>
84
+ * <ui-questionnaire-choice value="complete">Complete flow</ui-questionnaire-choice>
85
+ * </ui-questionnaire-choices>
86
+ * </ui-questionnaire-item>
87
+ * <ui-questionnaire-actions>
88
+ * <ui-questionnaire-previous></ui-questionnaire-previous>
89
+ * <ui-questionnaire-skip></ui-questionnaire-skip>
90
+ * <ui-questionnaire-next></ui-questionnaire-next>
91
+ * <ui-questionnaire-submit></ui-questionnaire-submit>
92
+ * </ui-questionnaire-actions>
93
+ * </ui-questionnaire>
94
+ * ```
95
+ */
96
+ import { WebComponent, html, prop, signal, nothing } from '@webjsdev/core';
97
+ import { cn, takeAuthoredClass } from '../lib/utils.ts';
98
+ import { buttonClass, type ButtonVariant, type ButtonSize } from './button.ts';
99
+
100
+ /** The form's classes (the element applies them). */
101
+ export const questionnaireClass = (): string => 'cn-questionnaire flex w-full min-w-0 flex-col';
102
+ /** The progress line (the element applies them). */
103
+ export const questionnaireProgressClass = (): string =>
104
+ 'cn-questionnaire-progress min-h-[1lh] w-fit min-w-[14ch] font-medium text-muted-foreground tabular-nums';
105
+ /** A question's fieldset (the element applies them). */
106
+ export const questionnaireItemClass = (): string => 'cn-questionnaire-item min-w-0 border-0 p-0 outline-none';
107
+ /** A question's title (the element applies them). */
108
+ // The styles space the title from what follows only when no description is its
109
+ // following sibling. Here the title and the description sit in their own part
110
+ // hosts, so they are never siblings, and the question is asked of the item
111
+ // instead: an item holding a description takes the margin back.
112
+ export const questionnaireTitleClass = (): string =>
113
+ 'cn-questionnaire-title cn-font-heading text-pretty [[data-slot=questionnaire-item]:has([data-slot=questionnaire-description])_&]:mb-0';
114
+ /** A question's description (the element applies them). */
115
+ export const questionnaireDescriptionClass = (): string =>
116
+ 'cn-questionnaire-description text-pretty text-muted-foreground';
117
+ /** The answers' box (the element applies them). */
118
+ export const questionnaireChoicesClass = (): string =>
119
+ 'cn-questionnaire-choices group/questionnaire-choices grid min-w-0';
120
+ /** One answer (the element applies them). */
121
+ export const questionnaireChoiceClass = (): string =>
122
+ 'cn-questionnaire-choice group/questionnaire-choice relative flex min-h-11 cursor-pointer items-start text-start transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50';
123
+ /** An answer's description line. */
124
+ export const questionnaireChoiceDescriptionClass = (): string => 'cn-questionnaire-choice-description';
125
+ /** The freeform answer (the element applies them). */
126
+ export const questionnaireInputClass = (): string =>
127
+ 'cn-questionnaire-input min-h-11 w-full min-w-0 transition-[color,box-shadow,background-color] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-0 selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground';
128
+ /** The error line (the element applies them). */
129
+ export const questionnaireErrorClass = (): string => 'cn-questionnaire-error text-destructive';
130
+ /** The actions row (the element applies them). */
131
+ export const questionnaireActionsClass = (): string =>
132
+ 'cn-questionnaire-actions grid min-h-11 w-full grid-cols-[minmax(0,1fr)_auto_auto] items-center';
133
+
134
+ const ROOT = 'ui-questionnaire';
135
+ const ITEM = 'ui-questionnaire-item';
136
+ let uid = 0;
137
+
138
+ function rootOf(el: Element): UiQuestionnaire | null {
139
+ return (typeof el.closest === 'function' ? el.closest(ROOT) : null) as UiQuestionnaire | null;
140
+ }
141
+
142
+ function itemOf(el: Element): UiQuestionnaireItem | null {
143
+ return (typeof el.closest === 'function' ? el.closest(ITEM) : null) as UiQuestionnaireItem | null;
144
+ }
145
+
146
+ // shadcn's className on each questionnaire part lands on the element that part
147
+ // renders, and no host draws a box of its own: the rendered elements are the
148
+ // flex and grid items the styles space (a hidden question's fieldset drops out
149
+ // of the form's gap, a button's column placement reaches the actions grid).
150
+ // No host hides itself, since every part hides its rendered element. The
151
+ // host's box is turned off through its style, which the server serves, unlike
152
+ // a class the element changes.
153
+ function takeAuthored(part: HTMLElement): string | null {
154
+ if (!part.getAttribute?.('style')) part.setAttribute?.('style', 'display: contents');
155
+ return takeAuthoredClass(part, null);
156
+ }
157
+
158
+ // A part connected after the root's first update asks it to push its state.
159
+ function announce(el: Element): void {
160
+ const root = rootOf(el);
161
+ if (root) root._partConnected();
162
+ }
163
+
164
+ function isTextEntry(target: EventTarget | null): boolean {
165
+ const el = target as HTMLElement | null;
166
+ if (!el || typeof el.matches !== 'function') return false;
167
+ return el.matches('textarea, input:not([type=radio]):not([type=checkbox]):not([type=button]):not([type=submit])');
168
+ }
169
+
170
+ // --------------------------------------------------------------------------
171
+ // <ui-questionnaire>
172
+ // --------------------------------------------------------------------------
173
+
174
+ export class UiQuestionnaire extends WebComponent({
175
+ action: String,
176
+ method: String,
177
+ shortcuts: prop<'' | 'letters' | 'numbers'>(String),
178
+ // The active question, by name: set it to go there.
179
+ item: String,
180
+ // The question the flow opens on.
181
+ defaultItem: prop(String, { attribute: 'default-item' }),
182
+ // Drive the author's own <form> instead of rendering one.
183
+ asChild: prop(Boolean, { attribute: 'as-child' }),
184
+ }) {
185
+ readonly #enhanced = signal(false);
186
+ readonly #active = signal('');
187
+ _queued = false;
188
+
189
+ constructor() {
190
+ super();
191
+ this.action = '';
192
+ this.method = '';
193
+ this.shortcuts = '';
194
+ this.item = '';
195
+ this.defaultItem = '';
196
+ this.asChild = false;
197
+ }
198
+
199
+ // The listeners sit on the host, which every event from the form reaches,
200
+ // so they serve the element's own form and an author's `as-child` one alike.
201
+ connectedCallback(): void {
202
+ super.connectedCallback?.();
203
+ this.addEventListener('submit', this._onSubmit);
204
+ this.addEventListener('keydown', this._onKeyDown);
205
+ this.addEventListener('change', this._onChange);
206
+ this.addEventListener('input', this._onChange);
207
+ this._partConnected();
208
+ }
209
+
210
+ disconnectedCallback(): void {
211
+ this.removeEventListener('submit', this._onSubmit);
212
+ this.removeEventListener('keydown', this._onKeyDown);
213
+ this.removeEventListener('change', this._onChange);
214
+ this.removeEventListener('input', this._onChange);
215
+ super.disconnectedCallback?.();
216
+ }
217
+
218
+ render() {
219
+ if (this.asChild) {
220
+ // The author's form is the questionnaire; this host only drives it.
221
+ if (typeof this.getAttribute === 'function' && !this.getAttribute('style')) this.setAttribute('style', 'display: contents');
222
+ return html`<slot></slot>`;
223
+ }
224
+ return html`<form
225
+ data-slot="questionnaire"
226
+ action=${this.action || nothing}
227
+ method=${this.method || nothing}
228
+ ?novalidate=${this.#enhanced.get()}
229
+ data-shortcuts=${this.shortcuts || nothing}
230
+ class=${cn(questionnaireClass(), takeAuthored(this))}
231
+ ><slot></slot></form>`;
232
+ }
233
+
234
+ /** The form the questionnaire runs on: its own, or the author's under `as-child`. */
235
+ form(): HTMLFormElement | null {
236
+ if (typeof this.querySelector !== 'function') return null;
237
+ return this.querySelector('form');
238
+ }
239
+
240
+ willUpdate(changedProperties: Map<string, unknown>): void {
241
+ super.willUpdate?.(changedProperties);
242
+ // The active question set from outside: go there, with no validation, as
243
+ // a controlled shadcn Questionnaire does.
244
+ if (changedProperties.has('item') && this.#enhanced.get() && this.item && this.item !== this.#active.get()) {
245
+ const target = this.items().find((i) => i.name === this.item);
246
+ if (target) queueMicrotask(() => this._go(target, { focus: false }));
247
+ }
248
+ }
249
+
250
+ _partConnected(): void {
251
+ if (this._queued) return;
252
+ this._queued = true;
253
+ queueMicrotask(() => {
254
+ this._queued = false;
255
+ this._enhance();
256
+ this.sync();
257
+ });
258
+ }
259
+
260
+ _enhance(): void {
261
+ if (this.#enhanced.get()) return;
262
+ const items = this.items();
263
+ const wanted = this.item || this.defaultItem;
264
+ const first = items.find((i) => i.name === wanted) ?? items[0];
265
+ if (!first) return;
266
+ this.#active.set(first.name);
267
+ this.item = first.name;
268
+ this.#enhanced.set(true);
269
+ // An author's form validates per question from here, as the element's own does.
270
+ if (this.asChild) {
271
+ const form = this.form();
272
+ if (form) {
273
+ form.noValidate = true;
274
+ if (this.shortcuts) form.setAttribute('data-shortcuts', this.shortcuts);
275
+ }
276
+ }
277
+ }
278
+
279
+ /** The questions in the flow, in order: every one not `disabled`. */
280
+ items(): UiQuestionnaireItem[] {
281
+ if (typeof this.querySelectorAll !== 'function') return [];
282
+ return Array.from(this.querySelectorAll<UiQuestionnaireItem>(ITEM)).filter((i) => rootOf(i) === this && !i.disabled);
283
+ }
284
+
285
+ _activeItem(): UiQuestionnaireItem | null {
286
+ const items = this.items();
287
+ return items.find((i) => i.name === this.#active.get()) ?? items[0] ?? null;
288
+ }
289
+
290
+ /** Whether a question has an answer: a checked choice or a filled text field. */
291
+ answered(item: UiQuestionnaireItem): boolean {
292
+ return item.inputs().some((i) => (i.type === 'radio' || i.type === 'checkbox' ? i.checked : i.value.trim() !== ''));
293
+ }
294
+
295
+ /** Push the active question, its neighbours and every answer into the parts. */
296
+ sync(): void {
297
+ const items = this.items();
298
+ const item = this._activeItem();
299
+ const at = item ? items.indexOf(item) : -1;
300
+ const last = at === items.length - 1;
301
+ for (const i of this._all(ITEM) as UiQuestionnaireItem[]) {
302
+ i.flow?.set({ enhanced: this.#enhanced.get(), active: i === item });
303
+ }
304
+ for (const c of this._all('ui-questionnaire-choice') as UiQuestionnaireChoice[]) c._syncChecked();
305
+ for (const p of this._all('ui-questionnaire-progress') as UiQuestionnaireProgress[]) {
306
+ p.progress?.set({ current: at + 1, total: items.length });
307
+ }
308
+ const show = (tag: string, visible: boolean) => {
309
+ for (const b of this._all(tag) as UiQuestionnaireButton[]) b.visible?.set(visible);
310
+ };
311
+ if (!this.#enhanced.get()) {
312
+ show('ui-questionnaire-previous', false);
313
+ show('ui-questionnaire-skip', false);
314
+ show('ui-questionnaire-next', false);
315
+ show('ui-questionnaire-submit', true);
316
+ return;
317
+ }
318
+ show('ui-questionnaire-previous', at > 0);
319
+ show('ui-questionnaire-skip', !!item && !item.required);
320
+ show('ui-questionnaire-next', items.length > 1 && !last);
321
+ show('ui-questionnaire-submit', items.length > 0 && last);
322
+ }
323
+
324
+ _all(tag: string): Element[] {
325
+ if (typeof this.querySelectorAll !== 'function') return [];
326
+ return Array.from(this.querySelectorAll(tag)).filter((el) => rootOf(el) === this);
327
+ }
328
+
329
+ _go(item: UiQuestionnaireItem | undefined, opts: { focus?: boolean } = {}): void {
330
+ if (!item) return;
331
+ if (item.name !== this.#active.get()) {
332
+ this.#active.set(item.name);
333
+ this.item = item.name;
334
+ this.dispatchEvent(new CustomEvent('ui-active-change', { bubbles: true, detail: { name: item.name } }));
335
+ }
336
+ this.sync();
337
+ // Focus the new question's first answer once it is shown.
338
+ if (opts.focus === false) return;
339
+ this.updateComplete.then(() => requestAnimationFrame(() => item.inputs().find((i) => !i.disabled)?.focus()));
340
+ }
341
+
342
+ // Whether every button of a kind is written `disabled` (and there is one).
343
+ _allDisabled(tag: string): boolean {
344
+ const buttons = this._all(tag) as UiQuestionnaireButton[];
345
+ return buttons.length > 0 && buttons.every((b) => b.disabled);
346
+ }
347
+
348
+ /** Check the active question; mark it and return false when it needs an answer. */
349
+ validate(item = this._activeItem()): boolean {
350
+ if (!item) return true;
351
+ const ok = !item.required || this.answered(item);
352
+ item.invalid?.set(!ok);
353
+ if (!ok) item.inputs().find((i) => !i.disabled)?.focus();
354
+ return ok;
355
+ }
356
+
357
+ /** Go to the next question, if the active one is answered or optional. */
358
+ next(): void {
359
+ const items = this.items();
360
+ const item = this._activeItem();
361
+ if (!item || !this.validate(item)) return;
362
+ this._go(items[items.indexOf(item) + 1]);
363
+ }
364
+
365
+ /** Go back one question. */
366
+ previous(): void {
367
+ const items = this.items();
368
+ const item = this._activeItem();
369
+ if (!item) return;
370
+ this._go(items[items.indexOf(item) - 1]);
371
+ }
372
+
373
+ /** Leave an optional question unanswered and go on. */
374
+ skip(): void {
375
+ const item = this._activeItem();
376
+ if (!item || item.required) return;
377
+ for (const i of item.inputs()) {
378
+ if (i.type === 'radio' || i.type === 'checkbox') i.checked = false;
379
+ else i.value = '';
380
+ }
381
+ item.invalid?.set(false);
382
+ const items = this.items();
383
+ const after = items[items.indexOf(item) + 1];
384
+ if (after) this._go(after);
385
+ else this.sync();
386
+ }
387
+
388
+ // Enter in the form, and the Submit button, both arrive here.
389
+ _onSubmit = (e: Event): void => {
390
+ if (!this.#enhanced.get() || (e.target as Element | null)?.closest?.(ROOT) !== this) return;
391
+ const items = this.items();
392
+ const item = this._activeItem();
393
+ if (item && items.indexOf(item) < items.length - 1) {
394
+ e.preventDefault();
395
+ // Enter steps on only while Next can be pressed.
396
+ if (!this._allDisabled('ui-questionnaire-next')) this.next();
397
+ return;
398
+ }
399
+ // On the last question every required one must be answered.
400
+ const missing = items.find((i) => i.required && !this.answered(i));
401
+ if (missing) {
402
+ e.preventDefault();
403
+ if (missing !== item) this._go(missing);
404
+ this.validate(missing);
405
+ }
406
+ };
407
+
408
+ _onKeyDown = (e: KeyboardEvent): void => {
409
+ if (!this.#enhanced.get() || !this.shortcuts || rootOf(e.target as Element) !== this || e.metaKey || e.ctrlKey || e.altKey || isTextEntry(e.target)) return;
410
+ const item = this._activeItem();
411
+ if (!item) return;
412
+ const choices = item.choices();
413
+ const k = e.key.toLowerCase();
414
+ const index = this.shortcuts === 'numbers' ? Number(k) - 1 : k.length === 1 ? k.charCodeAt(0) - 97 : -1;
415
+ const choice = Number.isInteger(index) && index >= 0 ? choices[index] : undefined;
416
+ const input = choice?.input();
417
+ if (!input || input.disabled) return;
418
+ e.preventDefault();
419
+ input.checked = input.type === 'checkbox' ? !input.checked : true;
420
+ input.dispatchEvent(new Event('change', { bubbles: true }));
421
+ };
422
+
423
+ // A pick or a keystroke: keep a single question's choices and text apart,
424
+ // clear its error once it is answered, and redraw.
425
+ _onChange = (e: Event): void => {
426
+ const target = e.target as HTMLInputElement;
427
+ const item = itemOf(target);
428
+ if (!item || rootOf(item) !== this) return;
429
+ if (!item.multiple) {
430
+ if (target.matches?.('[data-slot="questionnaire-input"]') && target.value.trim()) {
431
+ for (const i of item.inputs()) if (i.type === 'radio') i.checked = false;
432
+ } else if (target.type === 'radio' && target.checked) {
433
+ for (const i of item.inputs()) if (i.matches('[data-slot="questionnaire-input"]')) i.value = '';
434
+ }
435
+ }
436
+ if (item.invalid?.get() && this.answered(item)) item.invalid?.set(false);
437
+ this.sync();
438
+ };
439
+ }
440
+ UiQuestionnaire.register('ui-questionnaire');
441
+
442
+ // --------------------------------------------------------------------------
443
+ // <ui-questionnaire-progress>
444
+ // --------------------------------------------------------------------------
445
+
446
+ export class UiQuestionnaireProgress extends WebComponent {
447
+ readonly progress = signal<{ current: number; total: number }>({ current: 0, total: 0 });
448
+ constructor() {
449
+ super();
450
+ }
451
+
452
+ connectedCallback(): void {
453
+ super.connectedCallback?.();
454
+ announce(this);
455
+ }
456
+
457
+ render() {
458
+ const { current, total } = this.progress.get();
459
+ const label = total ? `Question ${current} of ${total}` : '';
460
+ return html`<div
461
+ data-slot="questionnaire-progress"
462
+ role="progressbar"
463
+ aria-label="Questionnaire progress"
464
+ aria-live="polite"
465
+ aria-valuemin=${total ? '1' : nothing}
466
+ aria-valuemax=${total ? String(total) : nothing}
467
+ aria-valuenow=${total ? String(current) : nothing}
468
+ aria-valuetext=${label || nothing}
469
+ class=${cn(questionnaireProgressClass(), takeAuthored(this))}
470
+ >${label}</div>`;
471
+ }
472
+ }
473
+ UiQuestionnaireProgress.register('ui-questionnaire-progress');
474
+
475
+ // --------------------------------------------------------------------------
476
+ // <ui-questionnaire-item>
477
+ // --------------------------------------------------------------------------
478
+
479
+ export class UiQuestionnaireItem extends WebComponent({
480
+ name: String,
481
+ required: Boolean,
482
+ multiple: Boolean,
483
+ disabled: Boolean,
484
+ }) {
485
+ readonly flow = signal<{ enhanced: boolean; active: boolean }>({ enhanced: false, active: false });
486
+ // The item's error line reads this signal, so it redraws when it changes.
487
+ readonly invalid = signal(false);
488
+ _id = '';
489
+
490
+ constructor() {
491
+ super();
492
+ this.name = '';
493
+ this.required = false;
494
+ this.multiple = false;
495
+ this.disabled = false;
496
+ }
497
+
498
+ connectedCallback(): void {
499
+ super.connectedCallback?.();
500
+ this._id ||= `ui-questionnaire-item-${++uid}`;
501
+ announce(this);
502
+ }
503
+
504
+ get titleId(): string {
505
+ return `${this._id}-title`;
506
+ }
507
+
508
+ get descriptionId(): string {
509
+ return `${this._id}-description`;
510
+ }
511
+
512
+ get errorId(): string {
513
+ return `${this._id}-error`;
514
+ }
515
+
516
+ /** The question's answer controls: its choices' inputs and its text field. */
517
+ inputs(): HTMLInputElement[] {
518
+ if (typeof this.querySelectorAll !== 'function') return [];
519
+ return Array.from(this.querySelectorAll<HTMLInputElement>('input[data-slot="questionnaire-choice-input"], input[data-slot="questionnaire-input"]'))
520
+ .filter((i) => itemOf(i) === this);
521
+ }
522
+
523
+ /** The question's choices, in order. */
524
+ choices(): UiQuestionnaireChoice[] {
525
+ if (typeof this.querySelectorAll !== 'function') return [];
526
+ return Array.from(this.querySelectorAll<UiQuestionnaireChoice>('ui-questionnaire-choice')).filter((c) => itemOf(c) === this);
527
+ }
528
+
529
+ /** Whether the item holds a `tag` part, or false on the server, where there is no DOM to query. */
530
+ _has(tag: string): boolean {
531
+ return typeof this.querySelector === 'function' && !!this.querySelector(tag);
532
+ }
533
+
534
+ render() {
535
+ const flow = this.flow.get();
536
+ const hide = flow.enhanced && !flow.active;
537
+ const hasTitle = this._has('ui-questionnaire-title');
538
+ const hasDescription = this._has('ui-questionnaire-description');
539
+ const described = [hasDescription ? this.descriptionId : '', this.invalid.get() ? this.errorId : ''].filter(Boolean).join(' ');
540
+ return html`<fieldset
541
+ data-slot="questionnaire-item"
542
+ ?disabled=${this.disabled}
543
+ ?data-active=${flow.active}
544
+ ?hidden=${hide}
545
+ ?inert=${hide}
546
+ aria-labelledby=${hasTitle && this._id ? this.titleId : nothing}
547
+ aria-describedby=${described || nothing}
548
+ aria-invalid=${this.invalid.get() ? 'true' : nothing}
549
+ class=${cn(questionnaireItemClass(), takeAuthored(this))}
550
+ ><slot></slot></fieldset>`;
551
+ }
552
+ }
553
+ UiQuestionnaireItem.register('ui-questionnaire-item');
554
+
555
+ // --------------------------------------------------------------------------
556
+ // <ui-questionnaire-title> and <ui-questionnaire-description>
557
+ // --------------------------------------------------------------------------
558
+
559
+ export class UiQuestionnaireTitle extends WebComponent {
560
+ connectedCallback(): void {
561
+ super.connectedCallback?.();
562
+ announce(this);
563
+ }
564
+
565
+ render() {
566
+ const item = itemOf(this);
567
+ return html`<legend data-slot="questionnaire-title" id=${item?._id ? item.titleId : nothing} class=${cn(questionnaireTitleClass(), takeAuthored(this))}><slot></slot></legend>`;
568
+ }
569
+ }
570
+ UiQuestionnaireTitle.register('ui-questionnaire-title');
571
+
572
+ export class UiQuestionnaireDescription extends WebComponent {
573
+ connectedCallback(): void {
574
+ super.connectedCallback?.();
575
+ announce(this);
576
+ }
577
+
578
+ render() {
579
+ const item = itemOf(this);
580
+ return html`<p data-slot="questionnaire-description" id=${item?._id ? item.descriptionId : nothing} class=${cn(questionnaireDescriptionClass(), takeAuthored(this))}><slot></slot></p>`;
581
+ }
582
+ }
583
+ UiQuestionnaireDescription.register('ui-questionnaire-description');
584
+
585
+ // --------------------------------------------------------------------------
586
+ // <ui-questionnaire-choices>, <ui-questionnaire-choice>, -choice-description
587
+ // --------------------------------------------------------------------------
588
+
589
+ export class UiQuestionnaireChoices extends WebComponent {
590
+ render() {
591
+ return html`<div data-slot="questionnaire-choices" class=${cn(questionnaireChoicesClass(), takeAuthored(this))}><slot></slot></div>`;
592
+ }
593
+ }
594
+ UiQuestionnaireChoices.register('ui-questionnaire-choices');
595
+
596
+ export class UiQuestionnaireChoice extends WebComponent({
597
+ value: String,
598
+ checked: Boolean,
599
+ disabled: Boolean,
600
+ }) {
601
+ readonly #live = signal(false);
602
+ #seeded = false;
603
+ constructor() {
604
+ super();
605
+ this.value = '';
606
+ this.checked = false;
607
+ this.disabled = false;
608
+ }
609
+
610
+ // Seed the drawn state from `checked` here, not in connectedCallback: SSR
611
+ // runs willUpdate but never connects, and a pre-checked choice has to be
612
+ // drawn checked in the first paint and with JavaScript off.
613
+ willUpdate(changedProperties: Map<string, unknown>): void {
614
+ super.willUpdate?.(changedProperties);
615
+ if (!this.#seeded || changedProperties.has('checked')) {
616
+ this.#seeded = true;
617
+ this.#live.set(this.checked);
618
+ }
619
+ }
620
+
621
+ connectedCallback(): void {
622
+ super.connectedCallback?.();
623
+ announce(this);
624
+ }
625
+
626
+ input(): HTMLInputElement | null {
627
+ return this.querySelector?.('input[data-slot="questionnaire-choice-input"]') ?? null;
628
+ }
629
+
630
+ _syncChecked(): void {
631
+ const on = !!this.input()?.checked;
632
+ if (on !== this.#live.get()) this.#live.set(on);
633
+ this.requestUpdate?.();
634
+ }
635
+
636
+ // This choice's shortcut key, from its place in the question.
637
+ _shortcut(): string {
638
+ const mode = rootOf(this)?.shortcuts;
639
+ const item = itemOf(this);
640
+ if (!mode || !item) return '';
641
+ const i = item.choices().indexOf(this);
642
+ if (i < 0) return '';
643
+ if (mode === 'numbers') return i < 9 ? String(i + 1) : '';
644
+ return i < 26 ? String.fromCharCode(97 + i) : '';
645
+ }
646
+
647
+ render() {
648
+ const item = itemOf(this);
649
+ const type = item?.multiple ? 'checkbox' : 'radio';
650
+ const shortcut = this._shortcut();
651
+ const disabled = this.disabled || !!item?.disabled;
652
+ return html`<label
653
+ data-slot="questionnaire-choice"
654
+ data-type=${type}
655
+ ?data-checked=${this.#live.get()}
656
+ ?data-unchecked=${!this.#live.get()}
657
+ ?data-disabled=${disabled}
658
+ data-shortcut=${shortcut || nothing}
659
+ class=${cn(questionnaireChoiceClass(), takeAuthored(this))}
660
+ ><input
661
+ data-slot="questionnaire-choice-input"
662
+ type=${type}
663
+ name=${item?.name || nothing}
664
+ value=${this.value}
665
+ ?checked=${this.checked}
666
+ ?disabled=${disabled}
667
+ ?required=${!!item?.required && type === 'radio'}
668
+ aria-keyshortcuts=${shortcut ? shortcut.toUpperCase() : nothing}
669
+ class="cn-questionnaire-choice-input absolute inset-0 z-10 size-full cursor-pointer opacity-0"
670
+ ><span aria-hidden="true" data-slot="questionnaire-choice-indicator"
671
+ class="cn-questionnaire-choice-indicator pointer-events-none relative flex shrink-0 items-center justify-center border group-data-[type=radio]/questionnaire-choice:rounded-full"
672
+ ><span data-slot="questionnaire-choice-indicator-dot"
673
+ class="cn-questionnaire-choice-indicator-dot hidden rounded-full group-data-[type=checkbox]/questionnaire-choice:hidden group-data-[checked]/questionnaire-choice:block"
674
+ ></span><svg data-slot="questionnaire-choice-indicator-check" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
675
+ class="cn-questionnaire-choice-indicator-check hidden group-data-[type=radio]/questionnaire-choice:hidden group-data-[checked]/questionnaire-choice:block"
676
+ ><path d="M20 6 9 17l-5-5"></path></svg></span><span data-slot="questionnaire-choice-label"
677
+ class="cn-questionnaire-choice-label cn-questionnaire-choice-content flex min-w-0 flex-1 flex-col leading-snug"
678
+ ><slot></slot></span><span data-slot="questionnaire-choice-shortcut" aria-hidden="true" ?hidden=${!shortcut}
679
+ class="cn-questionnaire-choice-shortcut cn-questionnaire-shortcut pointer-events-none ms-auto hidden shrink-0 group-data-[shortcut]/questionnaire-choice:inline-flex"
680
+ >${shortcut.toUpperCase()}</span></label>`;
681
+ }
682
+ }
683
+ UiQuestionnaireChoice.register('ui-questionnaire-choice');
684
+
685
+ export class UiQuestionnaireChoiceDescription extends WebComponent {
686
+ render() {
687
+ return html`<span data-slot="questionnaire-choice-description" class=${cn(questionnaireChoiceDescriptionClass(), takeAuthored(this))}><slot></slot></span>`;
688
+ }
689
+ }
690
+ UiQuestionnaireChoiceDescription.register('ui-questionnaire-choice-description');
691
+
692
+ // --------------------------------------------------------------------------
693
+ // <ui-questionnaire-input>
694
+ // --------------------------------------------------------------------------
695
+
696
+ export class UiQuestionnaireInput extends WebComponent({
697
+ placeholder: String,
698
+ }) {
699
+ constructor() {
700
+ super();
701
+ this.placeholder = '';
702
+ }
703
+
704
+ connectedCallback(): void {
705
+ super.connectedCallback?.();
706
+ announce(this);
707
+ }
708
+
709
+ render() {
710
+ const item = itemOf(this);
711
+ return html`<div data-slot="questionnaire-input-wrapper" class=${cn('cn-questionnaire-input-wrapper group/questionnaire-input relative min-w-0', takeAuthored(this))}><input
712
+ data-slot="questionnaire-input"
713
+ type="text"
714
+ name=${item?.name || nothing}
715
+ placeholder=${this.placeholder || nothing}
716
+ aria-label=${this.getAttribute?.('aria-label') || nothing}
717
+ ?disabled=${!!item?.disabled}
718
+ class=${questionnaireInputClass()}
719
+ ></div>`;
720
+ }
721
+ }
722
+ UiQuestionnaireInput.register('ui-questionnaire-input');
723
+
724
+ // --------------------------------------------------------------------------
725
+ // <ui-questionnaire-error>
726
+ // --------------------------------------------------------------------------
727
+
728
+ export class UiQuestionnaireError extends WebComponent {
729
+ connectedCallback(): void {
730
+ super.connectedCallback?.();
731
+ announce(this);
732
+ }
733
+
734
+ render() {
735
+ const item = itemOf(this);
736
+ const invalid = !!item?.invalid?.get();
737
+ const fallback = item?.required ? 'Choose an answer to continue.' : 'Choose an answer or skip this question.';
738
+ return html`<p
739
+ data-slot="questionnaire-error"
740
+ id=${item?._id ? item.errorId : nothing}
741
+ ?hidden=${!invalid}
742
+ role=${invalid ? 'alert' : nothing}
743
+ class=${cn(questionnaireErrorClass(), takeAuthored(this))}
744
+ ><slot>${fallback}</slot></p>`;
745
+ }
746
+ }
747
+ UiQuestionnaireError.register('ui-questionnaire-error');
748
+
749
+ // --------------------------------------------------------------------------
750
+ // <ui-questionnaire-actions> and the four buttons
751
+ // --------------------------------------------------------------------------
752
+
753
+ export class UiQuestionnaireActions extends WebComponent {
754
+ render() {
755
+ return html`<div data-slot="questionnaire-actions" class=${cn(questionnaireActionsClass(), takeAuthored(this))}><slot></slot></div>`;
756
+ }
757
+ }
758
+ UiQuestionnaireActions.register('ui-questionnaire-actions');
759
+
760
+ /** What the four navigation buttons share. */
761
+ export class UiQuestionnaireButton extends WebComponent({
762
+ variant: String,
763
+ size: String,
764
+ // Written by the author, as shadcn's `disabled` on a navigation button.
765
+ disabled: Boolean,
766
+ }) {
767
+ static part = '';
768
+ static label = '';
769
+ static place = '';
770
+ static submits = false;
771
+ static defaultVariant = 'outline';
772
+
773
+ // Submit shows from the first paint; the others wait for the flow.
774
+ readonly visible = signal((this.constructor as typeof UiQuestionnaireButton).submits);
775
+
776
+ constructor() {
777
+ super();
778
+ const ctor = this.constructor as typeof UiQuestionnaireButton;
779
+ this.variant = ctor.defaultVariant;
780
+ this.size = 'default';
781
+ this.disabled = false;
782
+ }
783
+
784
+ connectedCallback(): void {
785
+ super.connectedCallback?.();
786
+ announce(this);
787
+ }
788
+
789
+ render() {
790
+ const ctor = this.constructor as typeof UiQuestionnaireButton;
791
+ return html`<button
792
+ type=${ctor.submits ? 'submit' : 'button'}
793
+ data-slot=${`questionnaire-${ctor.part}`}
794
+ data-size=${this.size}
795
+ data-variant=${this.variant}
796
+ ?hidden=${!this.visible.get()}
797
+ ?disabled=${this.disabled}
798
+ aria-hidden=${this.visible.get() ? nothing : 'true'}
799
+ aria-keyshortcuts=${this.visible.get() && !this.disabled && (ctor.part === 'next' || ctor.part === 'submit') ? 'Enter' : nothing}
800
+ class=${cn(buttonClass({ variant: this.variant as ButtonVariant, size: this.size as ButtonSize }), ctor.place, takeAuthored(this))}
801
+ @click=${this._onClick}
802
+ ><slot>${ctor.label}</slot></button>`;
803
+ }
804
+
805
+ _onClick = (): void => {};
806
+ }
807
+
808
+ export class UiQuestionnairePrevious extends UiQuestionnaireButton {
809
+ static part = 'previous';
810
+ static label = 'Previous';
811
+ static place = 'cn-questionnaire-previous col-start-1 row-start-1 min-h-11 justify-self-start sm:min-h-0';
812
+ _onClick = (): void => rootOf(this)?.previous();
813
+ }
814
+ UiQuestionnairePrevious.register('ui-questionnaire-previous');
815
+
816
+ export class UiQuestionnaireSkip extends UiQuestionnaireButton {
817
+ static part = 'skip';
818
+ static label = 'Skip';
819
+ static place = 'cn-questionnaire-skip col-start-2 row-start-1 min-h-11 justify-self-end sm:min-h-0';
820
+ _onClick = (): void => rootOf(this)?.skip();
821
+ }
822
+ UiQuestionnaireSkip.register('ui-questionnaire-skip');
823
+
824
+ export class UiQuestionnaireNext extends UiQuestionnaireButton {
825
+ static part = 'next';
826
+ static label = 'Next';
827
+ static place = 'cn-questionnaire-next col-start-3 row-start-1 min-h-11 justify-self-end sm:min-h-0';
828
+ static defaultVariant = 'default';
829
+ _onClick = (): void => rootOf(this)?.next();
830
+ }
831
+ UiQuestionnaireNext.register('ui-questionnaire-next');
832
+
833
+ export class UiQuestionnaireSubmit extends UiQuestionnaireButton {
834
+ static part = 'submit';
835
+ static label = 'Submit';
836
+ static place = 'cn-questionnaire-submit col-start-3 row-start-1 min-h-11 justify-self-end sm:min-h-0';
837
+ static submits = true;
838
+ static defaultVariant = 'default';
839
+ }
840
+ UiQuestionnaireSubmit.register('ui-questionnaire-submit');