zabi-components 8.0.0 → 9.0.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 (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -1,15 +1,67 @@
1
1
  import type { Snippet } from "svelte";
2
- import type { HTMLAttributes } from "svelte/elements";
3
- import type { CardVariant, SizeVariant } from "../types/variants.js";
2
+ import type { HTMLAnchorAttributes, HTMLAttributes } from "svelte/elements";
3
+ import type { CardVariant, SizeVariant, SurfaceTone } from "../types/variants.js";
4
4
  type Props = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
5
5
  onclick?: (event: MouseEvent) => void | Promise<void>;
6
6
  size?: SizeVariant;
7
+ /** The look of a `neutral` card. With a `tone` or a `fill` the fill is the edge: no border, and a shadow only for `elevated`. */
7
8
  variant?: CardVariant;
9
+ /**
10
+ * What the card is filled with: `neutral` (the default, the card surface),
11
+ * `tint` (the brand tint), `brand` or `accent`. On `brand` and `accent`
12
+ * the text, links, controls and focus rings inside take the fill's label
13
+ * colour; a Card, field or Alert inside is a surface again.
14
+ */
15
+ tone?: SurfaceTone;
16
+ /**
17
+ * A fill the app chooses, any CSS colour (`var(--group-colour)`). Wins
18
+ * over `tone`. Set `onFill`, the label colour on it. The library cannot
19
+ * check that pair's contrast: the app must hold it to 4.5:1.
20
+ */
21
+ fill?: string;
22
+ /** The label colour on `fill`, any CSS colour. */
23
+ onFill?: string;
24
+ /**
25
+ * With `href` the card itself is an `<a>`: the whole card is the link,
26
+ * and it works without scripts. A link card must not contain other
27
+ * interactive elements (a link or a button in a link is invalid and
28
+ * unreachable); a card with actions inside takes `onclick` or none.
29
+ * With `onclick` too, it is a link and `onclick` runs on activation.
30
+ */
31
+ href?: string;
32
+ /** With `href`: where the link opens. */
33
+ target?: HTMLAnchorAttributes["target"];
34
+ /** With `href`: the link's relationship, such as `noopener`. */
35
+ rel?: string;
36
+ /**
37
+ * The card itself is a `<button>`: the whole card is the control, and
38
+ * `submit` posts the form around it without scripts. With `href`
39
+ * the card is a link and this is ignored. Like a link card it must not
40
+ * contain other interactive elements. `onclick`
41
+ * runs on a press, as on any button.
42
+ */
43
+ type?: "submit" | "button" | "reset";
44
+ /** With `type`: the name the button submits under. */
45
+ name?: string;
46
+ /** With `type`: what the button submits. */
47
+ value?: string;
48
+ /** With `type`: the id of the form it belongs to, when it is not inside one. */
49
+ form?: string;
50
+ /** With `type`: the card cannot be pressed, is dimmed, and is skipped by the keyboard. */
51
+ disabled?: boolean;
52
+ /**
53
+ * With `type`: the card's action is on its way, as `Button`'s and
54
+ * `Chip`'s `loading`. `aria-busy` and `aria-disabled` (not `disabled`),
55
+ * the press swallowed (it submits nothing and does not reach
56
+ * `onclick`), the pressed look held, the size and the Tab stop kept.
57
+ * No spinner. Ignored by a link card and a plain card.
58
+ */
59
+ loading?: boolean;
8
60
  fullWidth?: boolean;
9
61
  class?: string;
10
62
  /** @deprecated use `class` — kept so 7.x call sites keep working. */
11
63
  className?: string;
12
- /** Required when `onclick` is set (no visible label). */
64
+ /** Names the card when it is interactive and its content does not (the content names a link well enough in most cases). */
13
65
  ariaLabel?: string;
14
66
  children?: Snippet;
15
67
  };
@@ -34,7 +34,6 @@
34
34
  * Both are merged here so existing call sites keep working. */
35
35
  const className = $derived(cn(`${classAttr} ${legacyClass}`));
36
36
 
37
- const headingTag = $derived(`h${level}`);
38
37
  const headingClasses = $derived(() => {
39
38
  return "text-2xl font-semibold leading-none tracking-tight text-headline";
40
39
  });
@@ -42,9 +41,22 @@
42
41
 
43
42
  <header class={cn(`flex flex-col space-y-2 pb-4 ${className}`)} {...restProps}>
44
43
  {#if title}
45
- <svelte:element this={headingTag} class={headingClasses()}>
46
- {title}
47
- </svelte:element>
44
+ <!-- One branch per level, not a dynamic element: hydration takes a dynamic
45
+ element out and puts it back, which blurs a control inside it that the user
46
+ had already tabbed to. -->
47
+ {#if level === 1}
48
+ <h1 class={headingClasses()}>{title}</h1>
49
+ {:else if level === 2}
50
+ <h2 class={headingClasses()}>{title}</h2>
51
+ {:else if level === 3}
52
+ <h3 class={headingClasses()}>{title}</h3>
53
+ {:else if level === 4}
54
+ <h4 class={headingClasses()}>{title}</h4>
55
+ {:else if level === 5}
56
+ <h5 class={headingClasses()}>{title}</h5>
57
+ {:else}
58
+ <h6 class={headingClasses()}>{title}</h6>
59
+ {/if}
48
60
  {/if}
49
61
  {#if children}
50
62
  {@render children()}
@@ -1,8 +1,15 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLInputAttributes } from "svelte/elements";
4
+ import type { SemanticVariant } from "../types/variants.js";
2
5
  import SelectionControl from "./SelectionControl.svelte";
3
6
  import type { SelectionControlMarkProps } from "./SelectionControl.svelte";
4
7
 
5
- interface Props {
8
+ /** Other attributes (`required`, `data-*`, `aria-*`, ...) land on the `<input>`. */
9
+ type Props = Omit<
10
+ HTMLInputAttributes,
11
+ "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"
12
+ > & {
6
13
  /** Extra classes for the host element. */
7
14
  class?: string;
8
15
  id?: string;
@@ -15,7 +22,32 @@
15
22
  checked?: boolean;
16
23
  onChange?: (event: Event) => void;
17
24
  onchange?: (event: Event) => void;
18
- }
25
+ /**
26
+ * Drawn inside the one label, between the box and the label text, as
27
+ * decoration for the name `label` already gives. A leading image
28
+ * should have an empty alt: the label names the row.
29
+ */
30
+ leading?: Snippet;
31
+ /**
32
+ * A line under the label, tied to the box with `aria-describedby`.
33
+ * It is inside the label, so a tap on it ticks the box; it does not
34
+ * join the name.
35
+ */
36
+ description?: string;
37
+ /**
38
+ * Where the box sits in the row. `end` puts it after the leading, the
39
+ * label and the description, at the far edge; the row is still one
40
+ * label and fills the width it is given.
41
+ */
42
+ controlPosition?: "start" | "end";
43
+ variant?: SemanticVariant;
44
+ /** Status text under the control. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
45
+ message?: string;
46
+ /** Help text under the control, read out with it. */
47
+ hint?: string;
48
+ /** An error under the control. It marks the input invalid and is announced; it wins over `variant` and `message`. */
49
+ error?: string;
50
+ };
19
51
 
20
52
  let {
21
53
  class: className = "",
@@ -26,32 +58,57 @@
26
58
  loading = false,
27
59
  label = "",
28
60
  defaultChecked = false,
29
- checked = $bindable(defaultChecked),
61
+ checked = $bindable<Exclude<Props["checked"], undefined>>(),
30
62
  onChange,
31
63
  onchange,
64
+ leading,
65
+ description,
66
+ controlPosition = "start",
32
67
  ...restProps
33
68
  }: Props = $props();
69
+
70
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
71
+ // one that has a fallback (`props_invalid_value`), and a page that throws
72
+ // while it hydrates never becomes interactive. The default is applied
73
+ // here instead: at once, for the server and the first render, and again
74
+ // whenever a parent hands back `undefined`.
75
+ const applyDefaults = () => {
76
+ if (checked === undefined) checked = defaultChecked;
77
+ };
78
+ applyDefaults();
79
+ $effect.pre(applyDefaults);
34
80
  </script>
35
81
 
36
82
  {#snippet mark(props: SelectionControlMarkProps)}
37
- <span
38
- class="pointer-events-none absolute inline-block size-3 shrink-0 animate-spin rounded-full border-2 border-brand-500 border-t-transparent z-10 opacity-0 group-has-[aria-busy=true]/control:opacity-100 group-has-checked/control:border-base-50"
39
- aria-hidden="true"
40
- ></span>
41
- <svg
42
- class="absolute w-3 h-3 text-base-50 pointer-events-none z-10 opacity-0 group-has-checked/control:opacity-100"
43
- fill="none"
44
- stroke="currentColor"
45
- viewBox="0 0 24 24"
46
- aria-hidden="true"
47
- >
48
- <path
49
- stroke-linecap="round"
50
- stroke-linejoin="round"
51
- stroke-width="2.5"
52
- d="M5 13l4 4L19 7"
53
- />
54
- </svg>
83
+ <!-- While loading the ring stands in for the tick, so the two never overlap;
84
+ the fill still says whether the box is checked. The ring is only in the
85
+ DOM while busy, which is what lets it take Spinner's reduced-motion
86
+ pulse: a pulse animates opacity and would un-hide a ring that was
87
+ merely transparent. It is drawn in the text colour so the gap in it
88
+ survives: a hand-written border colour class repaints all four sides. -->
89
+ {#if props.loading}
90
+ <span
91
+ class="pointer-events-none absolute z-10 inline-block size-3 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent motion-reduce:animate-pulse {props.checked
92
+ ? 'text-action-primary'
93
+ : 'text-brand-500'}"
94
+ aria-hidden="true"
95
+ ></span>
96
+ {:else}
97
+ <svg
98
+ class="absolute w-3 h-3 text-action-primary pointer-events-none z-10 opacity-0 group-has-checked/control:opacity-100"
99
+ fill="none"
100
+ stroke="currentColor"
101
+ viewBox="0 0 24 24"
102
+ aria-hidden="true"
103
+ >
104
+ <path
105
+ stroke-linecap="round"
106
+ stroke-linejoin="round"
107
+ stroke-width="2.5"
108
+ d="M5 13l4 4L19 7"
109
+ />
110
+ </svg>
111
+ {/if}
55
112
  {/snippet}
56
113
 
57
114
  <SelectionControl
@@ -68,6 +125,9 @@
68
125
  {onChange}
69
126
  {onchange}
70
127
  {mark}
128
+ {leading}
129
+ {description}
130
+ {controlPosition}
71
131
  class={className}
72
132
  {...restProps}
73
133
  />
@@ -1,4 +1,8 @@
1
- interface Props {
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { SemanticVariant } from "../types/variants.js";
4
+ /** Other attributes (`required`, `data-*`, `aria-*`, ...) land on the `<input>`. */
5
+ type Props = Omit<HTMLInputAttributes, "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"> & {
2
6
  /** Extra classes for the host element. */
3
7
  class?: string;
4
8
  id?: string;
@@ -11,7 +15,32 @@ interface Props {
11
15
  checked?: boolean;
12
16
  onChange?: (event: Event) => void;
13
17
  onchange?: (event: Event) => void;
14
- }
18
+ /**
19
+ * Drawn inside the one label, between the box and the label text, as
20
+ * decoration for the name `label` already gives. A leading image
21
+ * should have an empty alt: the label names the row.
22
+ */
23
+ leading?: Snippet;
24
+ /**
25
+ * A line under the label, tied to the box with `aria-describedby`.
26
+ * It is inside the label, so a tap on it ticks the box; it does not
27
+ * join the name.
28
+ */
29
+ description?: string;
30
+ /**
31
+ * Where the box sits in the row. `end` puts it after the leading, the
32
+ * label and the description, at the far edge; the row is still one
33
+ * label and fills the width it is given.
34
+ */
35
+ controlPosition?: "start" | "end";
36
+ variant?: SemanticVariant;
37
+ /** Status text under the control. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
38
+ message?: string;
39
+ /** Help text under the control, read out with it. */
40
+ hint?: string;
41
+ /** An error under the control. It marks the input invalid and is announced; it wins over `variant` and `message`. */
42
+ error?: string;
43
+ };
15
44
  declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
16
45
  type Checkbox = ReturnType<typeof Checkbox>;
17
46
  export default Checkbox;
@@ -0,0 +1,351 @@
1
+ <script lang="ts">
2
+ import { getContext, onMount, tick, type Snippet } from "svelte";
3
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes, HTMLInputAttributes } from "svelte/elements";
4
+ import Check from "@lucide/svelte/icons/check";
5
+ import { cn } from "../util/cn.js";
6
+ import { groupBinding } from "../util/hydration.js";
7
+ import { CHIP_GROUP_CONTEXT_KEY, type ChipGroupContext } from "../util/chip.js";
8
+ import { TOUCH_HIT_AREA } from "../util/touch-target.js";
9
+
10
+ /**
11
+ * One type for the four forms, as Button has one for the button and the
12
+ * link: a union of the elements' attributes is too large to carry through
13
+ * a wrapper. Other attributes land on the element that is the control: the
14
+ * `<a>`, the `<button>` or the `<input>`; `class` is always the chip's.
15
+ */
16
+ type Props = Omit<HTMLButtonAttributes, "class" | "type" | "value" | "onchange" | "children"> & {
17
+ /** Makes the chip a link to this address, which works without scripts. */
18
+ href?: string;
19
+ target?: HTMLAnchorAttributes["target"];
20
+ rel?: HTMLAnchorAttributes["rel"];
21
+ download?: HTMLAnchorAttributes["download"];
22
+ hreflang?: HTMLAnchorAttributes["hreflang"];
23
+ /** With `href`: what `aria-current` says while `selected`. */
24
+ current?: "page" | "true" | "step" | "location" | "date" | "time";
25
+ /**
26
+ * `checkbox` or `radio`: a real input under the chip, which submits in
27
+ * a form without scripts. Without it (and without `href`) the chip is a
28
+ * toggle button. Inside a ChipGroup with a `type`, the group's.
29
+ * `submit` or `button`: a button that does not toggle. A press posts
30
+ * the form (`submit`, with `name` and `value`) or only calls `onclick`;
31
+ * `selected` is drawn as on a toggle and said with `aria-pressed`, and
32
+ * the app decides it, as a server's answer does.
33
+ */
34
+ type?: "checkbox" | "radio" | "submit" | "button";
35
+ /** Name the input is submitted under. Inside a ChipGroup with a `name`, the group's. */
36
+ name?: string;
37
+ /** What the input submits, and what a ChipGroup's `value` holds when it is chosen. */
38
+ value?: string;
39
+ /** The chip is chosen. Bindable; the one prop for `checked`, `aria-pressed` and `aria-current`. */
40
+ selected?: boolean;
41
+ required?: boolean;
42
+ form?: string;
43
+ /** 28, 32 or 40px tall. On a coarse pointer the touch target reaches 44px; the box does not grow. */
44
+ size?: "sm" | "md" | "lg";
45
+ /** An icon or an avatar before the label. */
46
+ leading?: Snippet;
47
+ /** A check icon before the label while selected. On for a checkbox and a toggle button, off for a link and a radio. */
48
+ checkmark?: boolean;
49
+ /**
50
+ * A button chip (`type="submit"`, `"button"` or a toggle) while its
51
+ * action is on its way, as Button's: `aria-busy` and `aria-disabled`,
52
+ * a spinner over the label, the press swallowed. The chip keeps its
53
+ * width and stays a Tab stop, so focus is not lost. Ignored by a link
54
+ * and by `checkbox` / `radio`, which are not actions.
55
+ */
56
+ loading?: boolean;
57
+ /** Called with the new `selected` when a person changes it. */
58
+ onchange?: (selected: boolean) => void;
59
+ class?: string;
60
+ children?: Snippet;
61
+ };
62
+
63
+ let {
64
+ href,
65
+ target,
66
+ rel,
67
+ download,
68
+ hreflang,
69
+ current = "true",
70
+ type: typeProp,
71
+ name: nameProp,
72
+ value,
73
+ selected = $bindable<Exclude<Props["selected"], undefined>>(),
74
+ disabled = false,
75
+ required,
76
+ form,
77
+ size = "md",
78
+ leading,
79
+ checkmark,
80
+ loading = false,
81
+ onchange,
82
+ onclick,
83
+ class: className = "",
84
+ children,
85
+ ...restProps
86
+ }: Props = $props();
87
+
88
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
89
+ // one that has a fallback (`props_invalid_value`), and a page that throws
90
+ // while it hydrates never becomes interactive. The default is applied
91
+ // here instead: at once, for the server and the first render, and again
92
+ // whenever a parent hands back `undefined`.
93
+ const applyDefaults = () => {
94
+ if (selected === undefined) selected = false;
95
+ };
96
+ applyDefaults();
97
+ $effect.pre(applyDefaults);
98
+
99
+ const group = getContext<ChipGroupContext | undefined>(CHIP_GROUP_CONTEXT_KEY);
100
+ /** Inside a group that has a `type`, the group decides what is chosen. */
101
+ const grouped = $derived(group?.type !== undefined && typeProp === undefined && href === undefined);
102
+ const type = $derived(grouped ? group?.type : typeProp);
103
+ const name = $derived(nameProp ?? (grouped ? group?.name : undefined));
104
+ const isLink = $derived(href !== undefined && href !== null);
105
+ const isInput = $derived(!isLink && (type === "checkbox" || type === "radio"));
106
+ /** A button that does not toggle: the app says what is selected. */
107
+ const isFixedButton = $derived(!isLink && (typeProp === "submit" || typeProp === "button"));
108
+ const isDisabled = $derived(!!disabled || (grouped && !!group?.disabled));
109
+ const chosen = $derived(grouped ? !!group?.isSelected(value ?? "") : !!selected);
110
+ const showCheckmark = $derived(checkmark ?? (!isLink && type !== "radio"));
111
+
112
+ function setChosen(next: boolean, report: boolean) {
113
+ if (grouped) group?.change(value ?? "", next, report);
114
+ else selected = next;
115
+ }
116
+
117
+ let input: HTMLInputElement | undefined = $state();
118
+
119
+ /**
120
+ * The input is bound, so a choice made before the page hydrated is kept:
121
+ * Svelte's binding finds the input changed and hands its state over here
122
+ * instead of overwriting it (see util/hydration.ts). A choice adopted that
123
+ * way came without a `change` event that anything heard, so it is reported
124
+ * once, with a real `change` on the input, when the component has settled:
125
+ * the callbacks and a form around it hear it as they hear a press.
126
+ */
127
+ let settling = true;
128
+ let adopted = false;
129
+
130
+ function take(next: boolean) {
131
+ if (next === chosen) return;
132
+ setChosen(next, false);
133
+ if (settling) adopted = true;
134
+ }
135
+
136
+ const NOT_CHOSEN = {};
137
+ /** A radio on its own: its own value while it is checked. */
138
+ const aloneBinding = groupBinding<unknown>(
139
+ () => (selected ? value : NOT_CHOSEN),
140
+ (picked) => take(picked === value),
141
+ () => take(false),
142
+ );
143
+ /** A radio in a group: the group's value. */
144
+ const groupedBinding = groupBinding<string>(
145
+ () => group?.radioValue,
146
+ (picked) => {
147
+ if (group?.radioValue === picked) return;
148
+ group?.change(picked, true, false);
149
+ if (settling) adopted = true;
150
+ },
151
+ () => group?.restore(),
152
+ );
153
+
154
+ onMount(() => {
155
+ // The bindings adopt in a microtask of their own, queued before this one.
156
+ void tick().then(() => {
157
+ settling = false;
158
+ if (adopted) input?.dispatchEvent(new Event("change", { bubbles: true }));
159
+ });
160
+ });
161
+
162
+ function handleInputChange(event: Event) {
163
+ if (isDisabled) return;
164
+ // A real change is its own report: nothing is left to tell.
165
+ adopted = false;
166
+ const checked = (event.currentTarget as HTMLInputElement).checked;
167
+ setChosen(checked, true);
168
+ onchange?.(checked);
169
+ }
170
+
171
+ /** Loading without `disabled`: unavailable, and still where focus is (see Button). */
172
+ const isBusy = $derived(loading && !disabled);
173
+
174
+ function handleButtonClick(event: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {
175
+ if (isBusy) {
176
+ // The press ends here: it does not submit, toggle or reach `onclick`.
177
+ event.preventDefault();
178
+ event.stopPropagation();
179
+ return;
180
+ }
181
+ onclick?.(event);
182
+ if (event.defaultPrevented || isDisabled) return;
183
+ if (isFixedButton) return;
184
+ selected = !selected;
185
+ onchange?.(selected);
186
+ }
187
+
188
+ /** A disabled link has no `href`, but a pointer can still press it. */
189
+ function handleLinkClick(event: MouseEvent) {
190
+ if (isDisabled) {
191
+ event.preventDefault();
192
+ event.stopPropagation();
193
+ return;
194
+ }
195
+ (onclick as ((event: MouseEvent) => void) | null | undefined)?.(event);
196
+ }
197
+
198
+ /**
199
+ * 28 / 32 / 40px, a fixed height: a chip never wraps its label, so one
200
+ * line is all it ever holds. The padding is 10 / 12 / 16px.
201
+ */
202
+ const SIZE = { sm: "h-7 px-[10px]", md: "h-8 px-3", lg: "h-10 px-4" } as const;
203
+ const FORCED = "forced-colors:outline-2 forced-colors:-outline-offset-2 forced-colors:outline-[color:Highlight]";
204
+
205
+ const base = $derived(
206
+ `group/chip relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-[6px] whitespace-nowrap rounded-pill text-sm transition-colors duration-(--duration-base) motion-reduce:transition-none ${SIZE[size] ?? SIZE.md} ${TOUCH_HIT_AREA} ${isDisabled ? "pointer-events-none cursor-not-allowed opacity-50" : ""}`,
207
+ );
208
+
209
+ /**
210
+ * A link and a button read `selected`. The hand-written chip colours are
211
+ * outside every layer, so they cannot be overridden by a variant of
212
+ * another colour: the two states are two class lists, never both.
213
+ */
214
+ const elementClasses = $derived(
215
+ cn(
216
+ base,
217
+ "focus-ring",
218
+ chosen
219
+ ? `bg-chip-selected hover:bg-chip-selected-hover active:bg-chip-selected-active text-chip-selected-text font-semibold ${FORCED}`
220
+ : "bg-chip hover:bg-chip-hover active:bg-chip-active text-chip-text font-medium",
221
+ isBusy && "cursor-progress",
222
+ className,
223
+ ),
224
+ );
225
+
226
+ /**
227
+ * An input is chosen by the browser, and the look follows its `:checked`
228
+ * (not `selected`), so a choice made before hydration shows at once.
229
+ * Every colour is a `has-` or `not-has-` variant: with the plain
230
+ * hand-written class on the label, the unlayered rule would win over the
231
+ * selected variant.
232
+ */
233
+ const labelClasses = $derived(
234
+ cn(
235
+ base,
236
+ "focus-ring font-medium not-has-[:checked]:bg-chip not-has-[:checked]:text-chip-text not-has-[:checked]:hover:bg-chip-hover not-has-[:checked]:active:bg-chip-active has-[:checked]:bg-chip-selected has-[:checked]:text-chip-selected-text has-[:checked]:font-semibold has-[:checked]:hover:bg-chip-selected-hover has-[:checked]:active:bg-chip-selected-active",
237
+ "has-[:checked]:forced-colors:outline-2 has-[:checked]:forced-colors:-outline-offset-2 has-[:checked]:forced-colors:outline-[color:Highlight]",
238
+ "has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-focus-ring",
239
+ className,
240
+ ),
241
+ );
242
+
243
+ /** The real input, stretched over the chip and transparent, so a press lands on it. */
244
+ const INPUT_CLASSES = "absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded-pill opacity-0 outline-none disabled:cursor-not-allowed";
245
+ </script>
246
+
247
+ {#snippet inner(checkClass: string)}
248
+ {#if showCheckmark}
249
+ <Check class="size-3.5 shrink-0 {checkClass}" aria-hidden="true" />
250
+ {/if}
251
+ {#if leading}
252
+ <span class="inline-flex shrink-0 items-center">{@render leading()}</span>
253
+ {/if}
254
+ {#if children}
255
+ <span class="min-w-0 truncate">{@render children()}</span>
256
+ {/if}
257
+ {/snippet}
258
+
259
+ {#if isLink}
260
+ <!-- A link that cannot be followed keeps its role and says it is unavailable, as Button's does. -->
261
+ <a
262
+ href={isDisabled ? undefined : href}
263
+ {target}
264
+ {rel}
265
+ {download}
266
+ {hreflang}
267
+ class={elementClasses}
268
+ role={isDisabled ? "link" : undefined}
269
+ aria-disabled={isDisabled ? "true" : undefined}
270
+ aria-current={chosen ? current : undefined}
271
+ {...restProps as unknown as HTMLAnchorAttributes}
272
+ onclick={handleLinkClick}
273
+ >
274
+ {@render inner(chosen ? "" : "hidden")}
275
+ </a>
276
+ {:else if isInput}
277
+ <label class={labelClasses} data-chip-form={type}>
278
+ <!-- One branch per type: a binding needs a type it can read. -->
279
+ {#if type === "checkbox"}
280
+ <input
281
+ bind:this={input}
282
+ type="checkbox"
283
+ {name}
284
+ {value}
285
+ {required}
286
+ {form}
287
+ bind:checked={() => chosen, take}
288
+ disabled={isDisabled}
289
+ class={INPUT_CLASSES}
290
+ onchange={handleInputChange}
291
+ {...restProps as unknown as HTMLInputAttributes}
292
+ />
293
+ {:else if grouped}
294
+ <input
295
+ bind:this={input}
296
+ type="radio"
297
+ {name}
298
+ {value}
299
+ {required}
300
+ {form}
301
+ bind:group={groupedBinding.value}
302
+ disabled={isDisabled}
303
+ class={INPUT_CLASSES}
304
+ onchange={handleInputChange}
305
+ {...restProps as unknown as HTMLInputAttributes}
306
+ />
307
+ {:else}
308
+ <input
309
+ bind:this={input}
310
+ type="radio"
311
+ {name}
312
+ {value}
313
+ {required}
314
+ {form}
315
+ bind:group={aloneBinding.value}
316
+ disabled={isDisabled}
317
+ class={INPUT_CLASSES}
318
+ onchange={handleInputChange}
319
+ {...restProps as unknown as HTMLInputAttributes}
320
+ />
321
+ {/if}
322
+ {@render inner("hidden group-has-checked/chip:block")}
323
+ </label>
324
+ {:else}
325
+ <button
326
+ type={typeProp === "submit" ? "submit" : "button"}
327
+ {name}
328
+ {value}
329
+ {form}
330
+ class={elementClasses}
331
+ {disabled}
332
+ aria-pressed={chosen}
333
+ aria-busy={isBusy ? "true" : undefined}
334
+ aria-disabled={isBusy ? "true" : undefined}
335
+ {...restProps}
336
+ onclick={handleButtonClick}
337
+ >
338
+ {#if isBusy}
339
+ <!-- The label stays in the chip, only unseen, so the width and the name do not change. -->
340
+ <span class="inline-flex min-w-0 items-center justify-center gap-[6px] opacity-0">
341
+ {@render inner(chosen ? "" : "hidden")}
342
+ </span>
343
+ <span
344
+ class="pointer-events-none absolute inline-block size-3.5 animate-spin rounded-full border-2 border-current border-t-transparent motion-reduce:animate-pulse"
345
+ aria-hidden="true"
346
+ ></span>
347
+ {:else}
348
+ {@render inner(chosen ? "" : "hidden")}
349
+ {/if}
350
+ </button>
351
+ {/if}