@webjsdev/ui 0.3.16 → 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 +71 -10
  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 +88 -0
  284. package/packages/registry/components/field.ts +165 -0
  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 +66 -0
  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 +4092 -60
  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,333 @@
1
+ /**
2
+ * Slider: pick a number, or a range, on a track. Tier 1 and Tier 2.
3
+ *
4
+ * Tier 1: `sliderClass()` styles one native `<input type="range">`, which is
5
+ * all a single value needs and works with no JavaScript at all.
6
+ *
7
+ * Tier 2: `<ui-slider>` ENHANCES one native range input per thumb (D13). You
8
+ * author the inputs, so without JavaScript the page shows them and the form
9
+ * submits them. With JavaScript the element hides them (visually only: they
10
+ * still submit and reset with the form) and draws shadcn's track, range and
11
+ * thumbs, writing every move back into the inputs. Two inputs make a range,
12
+ * and a thumb never passes its neighbour.
13
+ *
14
+ * shadcn parity:
15
+ * Slider → <ui-slider orientation> around one
16
+ * <input type="range" min max step value> per thumb
17
+ * defaultValue / value → each input's value
18
+ * min / max / step → the inputs' own attributes (read from the first)
19
+ * disabled → `disabled` on the inputs
20
+ * onValueChange → `ui-value-change` ({ detail: { value: number[] } });
21
+ * each input also fires `input`
22
+ * onValueCommit → `ui-value-commit` ({ detail: { value: number[] } });
23
+ * each moved input also fires `change`
24
+ *
25
+ * Keyboard (as Radix's): ArrowRight / ArrowUp step up, ArrowLeft / ArrowDown
26
+ * step down (ArrowLeft / ArrowRight swap under `dir="rtl"`), PageUp / PageDown
27
+ * and Shift with an arrow move ten steps, Home / End go to the ends. A press
28
+ * on the track moves the nearest thumb there and drags it.
29
+ *
30
+ * A11y (handled by the element, plus your part): each thumb is a focusable
31
+ * `role="slider"` with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and
32
+ * `aria-orientation`, named by its input's `<label>`s or `aria-label`, and a
33
+ * label click focuses its thumb. Your part: a name for every input, which for
34
+ * a range means one per thumb ("Minimum price", "Maximum price").
35
+ *
36
+ * @example
37
+ * ```html
38
+ * <input type="range" class=${sliderClass()} min="0" max="100" value="50" aria-label="Volume">
39
+ *
40
+ * <ui-slider>
41
+ * <input type="range" class=${sliderClass()} name="min" min="0" max="100" step="5" value="25" aria-label="Minimum">
42
+ * <input type="range" class=${sliderClass()} name="max" min="0" max="100" step="5" value="75" aria-label="Maximum">
43
+ * </ui-slider>
44
+ * ```
45
+ */
46
+ import { WebComponent, html, prop, signal, nothing } from '@webjsdev/core';
47
+ import { ensureId } from '../lib/utils.ts';
48
+
49
+ /** One native `<input type="range">`, styled to match the drawn slider. */
50
+ export function sliderClass(): string {
51
+ return [
52
+ 'h-4 w-full cursor-pointer appearance-none bg-transparent disabled:cursor-not-allowed disabled:opacity-50',
53
+ 'focus-visible:outline-hidden',
54
+ '[&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-muted',
55
+ '[&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-sm',
56
+ 'focus-visible:[&::-webkit-slider-thumb]:ring-4 focus-visible:[&::-webkit-slider-thumb]:ring-ring/50',
57
+ '[&::-moz-range-track]:h-1.5 [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-muted',
58
+ '[&::-moz-range-progress]:h-1.5 [&::-moz-range-progress]:rounded-full [&::-moz-range-progress]:bg-primary',
59
+ '[&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-primary [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:shadow-sm',
60
+ ].join(' ');
61
+ }
62
+
63
+ /** The drawn slider's root (the element applies them). */
64
+ export const sliderRootClass = (): string =>
65
+ 'cn-slider relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col';
66
+ /** The track (the element applies them). */
67
+ export const sliderTrackClass = (): string =>
68
+ 'cn-slider-track relative grow overflow-hidden data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full';
69
+ /** The filled range (the element applies them). */
70
+ export const sliderRangeClass = (): string =>
71
+ 'cn-slider-range absolute select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full';
72
+ /** A thumb (the element applies them). */
73
+ export const sliderThumbClass = (): string =>
74
+ 'cn-slider-thumb block shrink-0 select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50';
75
+
76
+ // The visual-only hide the select uses: still submitted and reset with the form.
77
+ const HIDDEN_CONTROL =
78
+ 'position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;opacity:0;pointer-events:none;overflow:hidden;';
79
+
80
+ export class UiSlider extends WebComponent({
81
+ orientation: prop<'horizontal' | 'vertical'>(String, { reflect: true }),
82
+ }) {
83
+ readonly #enhanced = signal(false);
84
+ _observer: MutationObserver | null = null;
85
+ _waiting: MutationObserver | null = null;
86
+ _form: HTMLFormElement | null = null;
87
+ _saved = new Map<HTMLInputElement, string | null>();
88
+ _drag = -1;
89
+ _moved = new Set<number>();
90
+
91
+ constructor() {
92
+ super();
93
+ this.orientation = 'horizontal';
94
+ }
95
+
96
+ /** The native range inputs, one per thumb. */
97
+ inputs(): HTMLInputElement[] {
98
+ return Array.from(this.querySelectorAll<HTMLInputElement>('input[type="range"]'));
99
+ }
100
+
101
+ /** The current values, one per thumb. */
102
+ get value(): number[] {
103
+ return this.inputs().map((i) => Number(i.value));
104
+ }
105
+
106
+ connectedCallback(): void {
107
+ super.connectedCallback?.();
108
+ queueMicrotask(() => {
109
+ if (this._enhance() || this._waiting) return;
110
+ this._waiting = new MutationObserver(() => {
111
+ if (this._enhance()) { this._waiting?.disconnect(); this._waiting = null; }
112
+ });
113
+ this._waiting.observe(this, { childList: true, subtree: true });
114
+ });
115
+ }
116
+
117
+ disconnectedCallback(): void {
118
+ this._waiting?.disconnect();
119
+ this._observer?.disconnect();
120
+ for (const [input, style] of this._saved) {
121
+ input.removeEventListener('focus', this._onInputFocus);
122
+ if (style !== null) input.setAttribute('style', style);
123
+ else input.removeAttribute('style');
124
+ input.removeAttribute('aria-hidden');
125
+ input.removeAttribute('tabindex');
126
+ }
127
+ this._saved.clear();
128
+ this._form?.removeEventListener('reset', this._onReset);
129
+ super.disconnectedCallback?.();
130
+ }
131
+
132
+ /** Take over from the native inputs. True once it has (or already had). */
133
+ _enhance(): boolean {
134
+ if (this.#enhanced.get()) return true;
135
+ const inputs = this.inputs();
136
+ if (!inputs.length) return false;
137
+ for (const input of inputs) {
138
+ const style = input.getAttribute('style');
139
+ this._saved.set(input, style);
140
+ input.setAttribute('style', (style ?? '') + HIDDEN_CONTROL);
141
+ input.setAttribute('aria-hidden', 'true');
142
+ input.tabIndex = -1;
143
+ input.addEventListener('focus', this._onInputFocus);
144
+ }
145
+ this._form = inputs[0].form;
146
+ this._form?.addEventListener('reset', this._onReset);
147
+ this._observer = new MutationObserver(() => { this.requestUpdate(); });
148
+ for (const input of inputs) this._observer.observe(input, { attributes: true });
149
+ this.#enhanced.set(true);
150
+ return true;
151
+ }
152
+
153
+ _onReset = (): void => {
154
+ queueMicrotask(() => { this.requestUpdate(); });
155
+ };
156
+
157
+ // A label click focuses the hidden input; the user means its thumb.
158
+ _onInputFocus = (e: Event): void => {
159
+ const i = this.inputs().indexOf(e.target as HTMLInputElement);
160
+ this._thumbs()[i]?.focus();
161
+ };
162
+
163
+ _thumbs(): HTMLElement[] {
164
+ return Array.from(this.querySelectorAll<HTMLElement>('[data-slot="slider-thumb"]'));
165
+ }
166
+
167
+ _bounds(): { min: number; max: number; step: number } {
168
+ const first = this.inputs()[0];
169
+ const min = Number(first?.min || 0);
170
+ const max = Number(first?.max || 100);
171
+ const step = Number(first?.step) > 0 ? Number(first.step) : 1;
172
+ return { min, max: max > min ? max : min + 1, step };
173
+ }
174
+
175
+ _rtl(): boolean {
176
+ return typeof getComputedStyle === 'function' && getComputedStyle(this).direction === 'rtl';
177
+ }
178
+
179
+ render() {
180
+ if (!this.#enhanced.get()) {
181
+ return html`<div data-slot="slider" data-orientation=${this.orientation || 'horizontal'} class="grid gap-2"><slot></slot></div>`;
182
+ }
183
+ const vertical = this.orientation === 'vertical';
184
+ const orientation = vertical ? 'vertical' : 'horizontal';
185
+ const inputs = this.inputs();
186
+ const { min, max } = this._bounds();
187
+ const pct = (v: number) => ((Math.min(max, Math.max(min, v)) - min) / (max - min)) * 100;
188
+ const values = inputs.map((i) => Number(i.value));
189
+ const disabled = inputs.some((i) => i.disabled);
190
+ const lo = values.length > 1 ? pct(Math.min(...values)) : 0;
191
+ const hi = pct(Math.max(...values));
192
+ const rangeStyle = vertical
193
+ ? `bottom:${lo}%;top:${100 - hi}%`
194
+ : `inset-inline-start:${lo}%;inset-inline-end:${100 - hi}%`;
195
+ const flip = this._rtl() ? 1 : -1;
196
+ return html`<div
197
+ data-slot="slider"
198
+ data-orientation=${orientation}
199
+ ?data-disabled=${disabled}
200
+ class=${sliderRootClass()}
201
+ @pointerdown=${this._onPointerDown}
202
+ @pointermove=${this._onPointerMove}
203
+ @pointerup=${this._onPointerUp}
204
+ @pointercancel=${this._onPointerUp}
205
+ ><slot></slot><span data-slot="slider-track" data-orientation=${orientation} class=${sliderTrackClass()}><span
206
+ data-slot="slider-range" data-orientation=${orientation} class=${sliderRangeClass()} style=${rangeStyle}
207
+ ></span></span>${inputs.map((input, i) => {
208
+ const p = pct(values[i]);
209
+ // At P% along the track, shifted back by P% of its own size, so a
210
+ // thumb sits inside the track at both ends, as Radix's does.
211
+ const place = vertical
212
+ ? `position:absolute;bottom:${p}%;transform:translateY(${p}%)`
213
+ : `position:absolute;inset-inline-start:${p}%;transform:translateX(${flip * p}%)`;
214
+ const labels = input.labels ? Array.from(input.labels).map((l) => ensureId(l, 'ui-slider-label')) : [];
215
+ return html`<span style=${place}><span
216
+ data-slot="slider-thumb"
217
+ role="slider"
218
+ tabindex=${input.disabled ? '-1' : '0'}
219
+ aria-valuemin=${String(min)}
220
+ aria-valuemax=${String(max)}
221
+ aria-valuenow=${String(values[i])}
222
+ aria-valuetext=${input.getAttribute('aria-valuetext') || nothing}
223
+ aria-orientation=${orientation}
224
+ aria-labelledby=${labels.length ? labels.join(' ') : nothing}
225
+ aria-label=${!labels.length && input.getAttribute('aria-label') ? input.getAttribute('aria-label') : nothing}
226
+ aria-disabled=${input.disabled ? 'true' : nothing}
227
+ ?data-disabled=${input.disabled}
228
+ data-orientation=${orientation}
229
+ class=${sliderThumbClass()}
230
+ @keydown=${(e: KeyboardEvent) => this._onKeyDown(e, i)}
231
+ ></span></span>`;
232
+ })}</div>`;
233
+ }
234
+
235
+ /** Move thumb `i` to `raw`, snapped to the step and kept between its neighbours. */
236
+ _set(i: number, raw: number): boolean {
237
+ const inputs = this.inputs();
238
+ const input = inputs[i];
239
+ if (!input || input.disabled) return false;
240
+ const { min, max, step } = this._bounds();
241
+ let v = Math.round((raw - min) / step) * step + min;
242
+ v = Number(v.toFixed(10));
243
+ const lower = i > 0 ? Number(inputs[i - 1].value) : min;
244
+ const upper = i < inputs.length - 1 ? Number(inputs[i + 1].value) : max;
245
+ v = Math.min(Math.max(v, lower, min), upper, max);
246
+ if (Number(input.value) === v) return false;
247
+ input.value = String(v);
248
+ input.dispatchEvent(new Event('input', { bubbles: true }));
249
+ this._moved.add(i);
250
+ this.requestUpdate();
251
+ this.dispatchEvent(new CustomEvent('ui-value-change', { bubbles: true, detail: { value: this.value } }));
252
+ return true;
253
+ }
254
+
255
+ // The end of a gesture: a key press, or a pointer release.
256
+ _commit(): void {
257
+ if (!this._moved.size) return;
258
+ const inputs = this.inputs();
259
+ for (const i of this._moved) inputs[i]?.dispatchEvent(new Event('change', { bubbles: true }));
260
+ this._moved.clear();
261
+ this.dispatchEvent(new CustomEvent('ui-value-commit', { bubbles: true, detail: { value: this.value } }));
262
+ }
263
+
264
+ _onKeyDown(e: KeyboardEvent, i: number): void {
265
+ const { min, max, step } = this._bounds();
266
+ const v = Number(this.inputs()[i]?.value);
267
+ const big = step * 10;
268
+ const rtl = this._rtl() && this.orientation !== 'vertical';
269
+ const up = (n: number) => this._set(i, v + n);
270
+ let handled = true;
271
+ switch (e.key) {
272
+ case 'ArrowUp': up(e.shiftKey ? big : step); break;
273
+ case 'ArrowDown': up(-(e.shiftKey ? big : step)); break;
274
+ case 'ArrowRight': up((rtl ? -1 : 1) * (e.shiftKey ? big : step)); break;
275
+ case 'ArrowLeft': up((rtl ? 1 : -1) * (e.shiftKey ? big : step)); break;
276
+ case 'PageUp': up(big); break;
277
+ case 'PageDown': up(-big); break;
278
+ case 'Home': this._set(i, min); break;
279
+ case 'End': this._set(i, max); break;
280
+ default: handled = false;
281
+ }
282
+ if (!handled) return;
283
+ e.preventDefault();
284
+ this._commit();
285
+ }
286
+
287
+ // The value under a pointer position, from the track's box.
288
+ _valueAt(e: PointerEvent): number {
289
+ const track = this.querySelector<HTMLElement>('[data-slot="slider-track"]');
290
+ const { min, max } = this._bounds();
291
+ if (!track) return min;
292
+ const r = track.getBoundingClientRect();
293
+ let t: number;
294
+ if (this.orientation === 'vertical') t = (r.bottom - e.clientY) / r.height;
295
+ else t = this._rtl() ? (r.right - e.clientX) / r.width : (e.clientX - r.left) / r.width;
296
+ return min + Math.min(1, Math.max(0, t)) * (max - min);
297
+ }
298
+
299
+ _onPointerDown = (e: PointerEvent): void => {
300
+ const inputs = this.inputs();
301
+ if (!inputs.length || inputs.every((i) => i.disabled) || e.button !== 0) return;
302
+ const v = this._valueAt(e);
303
+ const values = inputs.map((i) => Number(i.value));
304
+ // The nearest thumb; between two at the same spot, the one the press is
305
+ // past, so a range collapsed to one point can still be pulled apart.
306
+ let best = 0;
307
+ values.forEach((x, i) => {
308
+ const d = Math.abs(x - v);
309
+ const bestD = Math.abs(values[best] - v);
310
+ if (d < bestD || (d === bestD && v > x)) best = i;
311
+ });
312
+ if (inputs[best].disabled) return;
313
+ e.preventDefault();
314
+ this._drag = best;
315
+ // A pointer the engine does not track (a synthetic one) cannot be captured.
316
+ try { (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId); } catch { /* drag without capture */ }
317
+ this._set(best, v);
318
+ this._thumbs()[best]?.focus();
319
+ };
320
+
321
+ _onPointerMove = (e: PointerEvent): void => {
322
+ if (this._drag < 0) return;
323
+ this._set(this._drag, this._valueAt(e));
324
+ };
325
+
326
+ _onPointerUp = (e: PointerEvent): void => {
327
+ if (this._drag < 0) return;
328
+ this._drag = -1;
329
+ try { (e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId); } catch { /* already released */ }
330
+ this._commit();
331
+ };
332
+ }
333
+ UiSlider.register('ui-slider');
@@ -23,6 +23,19 @@
23
23
  * `action`: { label, onClick } | undefined. Renders an action button.
24
24
  * `cancel`: { label, onClick } | undefined. Renders a cancel button.
25
25
  * Both buttons dismiss the toast after running their onClick.
26
+ * `position`: one of the viewport positions. The toast shows at that
27
+ * corner instead of the viewport's own (shadcn's per-toast
28
+ * `position`).
29
+ *
30
+ * Firing a toast with no script of your own: any element carrying
31
+ * `data-toast="<message>"` fires one when clicked, as shadcn's demos do with
32
+ * `onClick={() => toast(...)}`. A mounted `<ui-sonner>` handles the clicks
33
+ * for the whole page through one delegated listener. The optional attributes
34
+ * are `data-toast-type` (`success` | `error` | `info` | `warning` |
35
+ * `loading`), `data-toast-description`, `data-toast-position`,
36
+ * `data-toast-duration` (ms), and `data-toast-action` (a button label; the
37
+ * button dismisses the toast). Put it on a real `<button type="button">` so it
38
+ * is focusable and fires from the keyboard too.
26
39
  *
27
40
  * Events: none dispatched (consumers act on the id returned by `toast()`).
28
41
  *
@@ -83,6 +96,7 @@ interface ToastOptions {
83
96
  duration?: number;
84
97
  action?: { label: string; onClick: () => void };
85
98
  cancel?: { label: string; onClick: () => void };
99
+ position?: SonnerPosition;
86
100
  }
87
101
 
88
102
  interface ToastItem extends ToastOptions {
@@ -126,9 +140,47 @@ function makeToast(message: string, opts: ToastOptions = {}, type: ToastType = '
126
140
  duration: opts.duration ?? (type === 'loading' ? 0 : 4000),
127
141
  action: opts.action,
128
142
  cancel: opts.cancel,
143
+ position: opts.position,
129
144
  });
130
145
  }
131
146
 
147
+ const TOAST_TYPES = ['default', 'success', 'error', 'info', 'warning', 'loading'];
148
+
149
+ /**
150
+ * A click on (or inside) an element carrying `data-toast` fires the toast it
151
+ * describes. One listener serves the page, however many viewports are
152
+ * mounted, and it routes through the same bus `toast()` uses.
153
+ */
154
+ function onToastClick(e: Event): void {
155
+ const target = e.target as Element | null;
156
+ const el = target && typeof target.closest === 'function' ? target.closest('[data-toast]') : null;
157
+ if (!el) return;
158
+ const message = el.getAttribute('data-toast') || '';
159
+ if (!message) return;
160
+ const rawType = el.getAttribute('data-toast-type') || 'default';
161
+ const type = (TOAST_TYPES.includes(rawType) ? rawType : 'default') as ToastType;
162
+ const position = el.getAttribute('data-toast-position') as SonnerPosition | null;
163
+ const duration = Number(el.getAttribute('data-toast-duration'));
164
+ const action = el.getAttribute('data-toast-action');
165
+ makeToast(message, {
166
+ description: el.getAttribute('data-toast-description') || undefined,
167
+ position: position && position in POSITIONS ? position : undefined,
168
+ duration: el.hasAttribute('data-toast-duration') && Number.isFinite(duration) ? duration : undefined,
169
+ action: action ? { label: action, onClick: () => {} } : undefined,
170
+ }, type);
171
+ }
172
+
173
+ /** Install the page's one `data-toast` listener, counted per mounted viewport. */
174
+ function holdToastClicks(): () => void {
175
+ const g = globalThis as unknown as { __webjsSonnerClicks?: number };
176
+ if (!g.__webjsSonnerClicks) document.addEventListener('click', onToastClick);
177
+ g.__webjsSonnerClicks = (g.__webjsSonnerClicks ?? 0) + 1;
178
+ return () => {
179
+ g.__webjsSonnerClicks = (g.__webjsSonnerClicks ?? 1) - 1;
180
+ if (!g.__webjsSonnerClicks) document.removeEventListener('click', onToastClick);
181
+ };
182
+ }
183
+
132
184
  export function toast(message: string, opts?: ToastOptions): string | number {
133
185
  return makeToast(message, opts, 'default');
134
186
  }
@@ -159,11 +211,11 @@ toast.promise = <T,>(p: Promise<T>, opts: { loading: string; success: string; er
159
211
  // --------------------------------------------------------------------------
160
212
 
161
213
  const POSITIONS = {
162
- 'top-right': 'top-4 right-4',
163
- 'top-left': 'top-4 left-4',
214
+ 'top-right': 'top-4 end-4',
215
+ 'top-left': 'top-4 start-4',
164
216
  'top-center': 'top-4 left-1/2 -translate-x-1/2',
165
- 'bottom-right': 'bottom-4 right-4',
166
- 'bottom-left': 'bottom-4 left-4',
217
+ 'bottom-right': 'bottom-4 end-4',
218
+ 'bottom-left': 'bottom-4 start-4',
167
219
  'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
168
220
  } as const;
169
221
 
@@ -184,9 +236,10 @@ const TYPE_ICON_COLOR: Record<ToastType, string> = {
184
236
  export class UiSonner extends WebComponent({
185
237
  position: prop<SonnerPosition>(String, { reflect: true }),
186
238
  }) {
187
- items = signal<ToastItem[]>([]);
239
+ readonly items = signal<ToastItem[]>([]);
188
240
 
189
241
  _disposeBeforeCache?: () => void;
242
+ _releaseClicks?: () => void;
190
243
 
191
244
  constructor() {
192
245
  super();
@@ -198,10 +251,13 @@ export class UiSonner extends WebComponent({
198
251
  // Clear pending toasts before a back/forward snapshot so a restored page
199
252
  // does not show stale toasts that should have auto-dismissed (#766).
200
253
  this._disposeBeforeCache = onBeforeCache(() => this.items.set([]));
254
+ this._releaseClicks = holdToastClicks();
201
255
  }
202
256
 
203
257
  disconnectedCallback(): void {
204
258
  this._disposeBeforeCache?.();
259
+ this._releaseClicks?.();
260
+ this._releaseClicks = undefined;
205
261
  super.disconnectedCallback?.();
206
262
  }
207
263
 
@@ -235,6 +291,7 @@ export class UiSonner extends WebComponent({
235
291
  duration: opts.duration ?? (type === 'loading' ? 0 : 4000),
236
292
  action: opts.action,
237
293
  cancel: opts.cancel,
294
+ position: opts.position,
238
295
  });
239
296
  return id;
240
297
  }
@@ -251,7 +308,16 @@ export class UiSonner extends WebComponent({
251
308
  }
252
309
 
253
310
  render() {
254
- const pos = POSITIONS[this.position] ?? POSITIONS['bottom-right'];
311
+ const own = this.position in POSITIONS ? this.position : 'bottom-right';
312
+ const items = this.items.get();
313
+ // A toast with its own position shows at that corner, in a region of its
314
+ // own; the viewport's region stays in the DOM from the first render.
315
+ const elsewhere = [...new Set(items.map((i) => i.position).filter((p): p is SonnerPosition => !!p && p !== own && p in POSITIONS))];
316
+ return html`${this._region(own, items.filter((i) => !i.position || i.position === own || !(i.position in POSITIONS)))}${elsewhere.map((p) => this._region(p, items.filter((i) => i.position === p)))}`;
317
+ }
318
+
319
+ _region(position: SonnerPosition, items: ToastItem[]) {
320
+ const pos = POSITIONS[position];
255
321
  // The container is a persistent live region: it is in the DOM from the
256
322
  // first render (even with zero toasts), so a screen reader announces
257
323
  // each toast as it is inserted. It is polite, and it is the ONLY live
@@ -266,6 +332,7 @@ export class UiSonner extends WebComponent({
266
332
  // polite viewport, and its second live root is the accepted cost.
267
333
  return html`<div
268
334
  data-slot="sonner"
335
+ data-position=${position}
269
336
  role="region"
270
337
  aria-label="Notifications"
271
338
  aria-live="polite"
@@ -274,7 +341,7 @@ export class UiSonner extends WebComponent({
274
341
  class=${`pointer-events-none fixed z-[100] flex flex-col gap-2 ${pos}`}
275
342
  >
276
343
  ${repeat(
277
- this.items.get(),
344
+ items,
278
345
  (item) => item.id,
279
346
  // The role is BRANCHED rather than written as a nullish hole. A hole
280
347
  // that evaluates to null serves role="" from the server renderer while
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Spinner: an animated busy indicator. Tier-1 helpers, `spinnerClass()` for
3
+ * the classes and `spinner()` for the whole inline SVG (the Lucide
4
+ * loader-circle path, so no icon package is needed). It inherits the text
5
+ * colour through `currentColor`, so it matches whatever it sits in.
6
+ *
7
+ * shadcn parity:
8
+ * Spinner → spinner({ class, label }) (the inline SVG)
9
+ * → spinnerClass() (the classes, for your own SVG)
10
+ *
11
+ * Size it with a size utility: `spinner({ class: 'size-6' })` replaces the
12
+ * default `size-4`.
13
+ *
14
+ * Design tokens used: none (it draws in `currentColor`).
15
+ *
16
+ * A11y (required for accessible output):
17
+ * `spinner()` renders `role="status"` + `aria-label="Loading"`, so a
18
+ * standalone spinner is announced. Pass `label` for a more specific name
19
+ * (`spinner({ label: 'Loading invoices' })`).
20
+ * Inside a BUTTON while a save is running, mark the button itself busy:
21
+ * `disabled` (so a second click cannot submit twice) plus `aria-busy="true"`,
22
+ * and keep a visible text label ("Saving"). The button's text already says
23
+ * what is happening, so pass `decorative: true`, which renders the SVG with
24
+ * `aria-hidden="true"` and no role, rather than nesting a second live status
25
+ * inside the button's name. Re-enable the button and drop `aria-busy` when
26
+ * the save settles, and report the outcome somewhere that is announced (a
27
+ * toast, or the form's error text).
28
+ * A spinner that replaces a whole region is better as a skeleton when the
29
+ * final layout is known, with `aria-busy="true"` on the region.
30
+ *
31
+ * @example
32
+ * ```html
33
+ * <div class="flex items-center gap-2 text-sm text-muted-foreground">
34
+ * ${spinner()} Loading invoices
35
+ * </div>
36
+ *
37
+ * <button class=${buttonClass()} type="submit" disabled aria-busy="true">
38
+ * ${spinner({ decorative: true })} Saving
39
+ * </button>
40
+ * ```
41
+ */
42
+ import { html, nothing } from '@webjsdev/core';
43
+ import { cn } from '../lib/utils.ts';
44
+
45
+ /** Spinner classes: `size-4` plus `animate-spin`. Apply to your own `<svg>`. */
46
+ export const spinnerClass = (): string => 'size-4 animate-spin';
47
+
48
+ /**
49
+ * The spinner as an inline SVG template. `class` is merged over
50
+ * `spinnerClass()` (a later size utility wins), `label` names it (default
51
+ * "Loading"), and `decorative` hides it from assistive tech for use beside
52
+ * visible text such as a busy button's label.
53
+ */
54
+ export function spinner(opts: { class?: string; label?: string; decorative?: boolean; icon?: 'inline-start' | 'inline-end' } = {}) {
55
+ const cls = cn(spinnerClass(), opts.class);
56
+ // shadcn's `data-icon`, which a button or badge reads to tighten its padding
57
+ // on the spinner's side. Left out, the attribute is omitted.
58
+ const icon = opts.icon ?? nothing;
59
+ // Branched rather than a nullish `role=${...}` hole: the server renderer
60
+ // stringifies a null attribute value, so a single template would serve
61
+ // `role=""` on the decorative form.
62
+ if (opts.decorative) {
63
+ return html`<svg data-slot="spinner" data-icon=${icon} aria-hidden="true" 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" class=${cls}><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>`;
64
+ }
65
+ return html`<svg data-slot="spinner" data-icon=${icon} role="status" aria-label=${opts.label ?? 'Loading'} 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" class=${cls}><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>`;
66
+ }