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,10 +1,37 @@
1
1
  <script lang="ts">
2
- import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
2
+ import { zabiCommonStrings } from "../util/zabi-strings.js";
3
+ import Eye from "@lucide/svelte/icons/eye";
4
+ import EyeOff from "@lucide/svelte/icons/eye-off";
5
+ import { tick, type Snippet } from "svelte";
6
+ import type { HTMLInputAttributes } from "svelte/elements";
3
7
  import type { SemanticVariant, SizeVariant } from "../types/variants.js";
4
- import { generateId } from "../util/ssr-safe.js";
5
8
  import { cn } from "../util/cn.js";
9
+ import { fieldDescribedBy, fieldMessageState } from "../util/field.js";
10
+ import { generateId } from "../util/ssr-safe.js";
11
+ import { TOUCH_HIT_AREA } from "../util/touch-target.js";
12
+ import FieldMessages from "./FieldMessages.svelte";
13
+ import IconButton from "./IconButton.svelte";
6
14
 
7
- interface Props {
15
+ /**
16
+ * Other attributes (`autocomplete`, `inputmode`, `maxlength`,
17
+ * `enterkeyhint`, `data-*`, `onchange`, ...) land on the `<input>`.
18
+ */
19
+ type Props = Omit<
20
+ HTMLInputAttributes,
21
+ | "class"
22
+ | "size"
23
+ | "value"
24
+ | "type"
25
+ | "id"
26
+ | "name"
27
+ | "placeholder"
28
+ | "required"
29
+ | "disabled"
30
+ | "oninput"
31
+ | "onblur"
32
+ | "aria-label"
33
+ | "aria-describedby"
34
+ > & {
8
35
  /** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
9
36
  id?: string;
10
37
  value?: string;
@@ -20,15 +47,40 @@
20
47
  loading?: boolean;
21
48
  size?: SizeVariant;
22
49
  variant?: SemanticVariant;
50
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
23
51
  message?: string;
52
+ /** Help text under the field, read out with it. */
53
+ hint?: string;
54
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
55
+ error?: string;
56
+ /**
57
+ * Drawn inside the field, before the text: an icon, a unit. Decoration
58
+ * lets a press through to the field; a button or link in it is pressable.
59
+ */
60
+ leading?: Snippet;
61
+ /** Drawn inside the field, after the text and before the loading spinner's place. */
62
+ trailing?: Snippet;
63
+ /**
64
+ * With `type="password"`: a button at the end of the field that shows
65
+ * the password as text, and hides it again. On by default; `false`
66
+ * leaves it out. Ignored for other types.
67
+ */
68
+ revealable?: boolean;
69
+ /**
70
+ * Accessible name of that button. It stays the same in both states;
71
+ * the button's pressed state says whether the password is showing.
72
+ */
73
+ revealLabel?: string;
24
74
  oninput?: (event: Event) => void;
25
75
  onblur?: (event: Event) => void;
26
76
  "aria-label"?: string;
27
- }
77
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
78
+ "aria-describedby"?: string | null;
79
+ };
28
80
 
29
81
  let {
30
82
  id: idProp,
31
- value = $bindable(""),
83
+ value = $bindable<Exclude<Props["value"], undefined>>(),
32
84
  type = "text",
33
85
  name = "",
34
86
  class: className = "",
@@ -41,43 +93,161 @@
41
93
  size = "md",
42
94
  variant = "default",
43
95
  message = "",
96
+ hint = "",
97
+ error = "",
98
+ leading,
99
+ trailing,
100
+ revealable = true,
101
+ revealLabel: revealLabelGiven,
44
102
  oninput,
45
103
  onblur,
104
+ "aria-describedby": describedBy,
46
105
  ...restProps
47
106
  }: Props = $props();
48
107
 
108
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
109
+ // one that has a fallback (`props_invalid_value`), and a page that throws
110
+ // while it hydrates never becomes interactive. The default is applied
111
+ // here instead: at once, for the server and the first render, and again
112
+ // whenever a parent hands back `undefined`.
113
+ const applyDefaults = () => {
114
+ if (value === undefined) value = "";
115
+ };
116
+ applyDefaults();
117
+ $effect.pre(applyDefaults);
118
+
119
+ /** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
120
+ const common = zabiCommonStrings();
121
+ const revealLabel = $derived(revealLabelGiven ?? common().showPassword);
122
+
49
123
  const fallbackId = generateId("input");
50
124
  const inputId = $derived(idProp ?? fallbackId);
51
125
  const isDisabled = $derived(disabled || loading);
52
126
 
127
+ const status = $derived(fieldMessageState({ variant, message, hint, error }));
128
+
129
+ /** Only a password can be revealed: on any other type the button would have nothing to do. */
130
+ const canReveal = $derived(revealable && type === "password");
131
+ let revealed = $state(false);
132
+ const shownType = $derived(canReveal && revealed ? "text" : type);
133
+
134
+ let input: HTMLInputElement | undefined = $state();
135
+
136
+ /**
137
+ * Changing `type` can drop the caret in some browsers. When the field has
138
+ * focus (a mouse or a finger pressed the button: see `keepFocus`), the
139
+ * selection is put back where it was.
140
+ */
141
+ async function restoreCaret() {
142
+ const field = input;
143
+ if (!field || document.activeElement !== field) return;
144
+ const { selectionStart, selectionEnd, selectionDirection } = field;
145
+ if (selectionStart === null || selectionEnd === null) return;
146
+ const restore = () => {
147
+ if (document.activeElement !== field) return;
148
+ try {
149
+ field.setSelectionRange(selectionStart, selectionEnd, selectionDirection ?? undefined);
150
+ } catch {
151
+ // A type without a text selection: nothing to put back.
152
+ }
153
+ };
154
+ await tick();
155
+ restore();
156
+ // Chromium moves the caret to the start once more after the type has
157
+ // changed, so it is put back again when the frame is done.
158
+ requestAnimationFrame(restore);
159
+ }
160
+
161
+ /**
162
+ * A press with a pointer does not take focus from the field: on a phone
163
+ * that would close the keyboard in the middle of typing a password. From
164
+ * the keyboard the button has focus and keeps it.
165
+ */
166
+ function keepFocus(event: Event) {
167
+ event.preventDefault();
168
+ }
169
+
53
170
  /**
54
171
  * Same fixed height scale as Button, IconButton and Select (32 / 40 / 48),
55
172
  * so a field and its submit button are the same height in a row. These
56
173
  * used to be 38 / 46 / 50 while Button was 40 / 48 / 64.
174
+ *
175
+ * Below the `sm` breakpoint the text is 16px at every size: iOS Safari
176
+ * zooms the page when a focused field is smaller. Only the font grows;
177
+ * the box keeps its height.
57
178
  */
58
179
  const sizeClass = $derived(() => {
59
180
  if (size === "sm") {
60
- return { box: "h-8 px-3", text: "text-sm", spinner: "size-3.5" };
181
+ return { box: "h-8 px-3 pointer-coarse:min-h-11", text: "text-sm max-sm:text-base", spinner: "size-3.5" };
61
182
  } else if (size === "lg") {
62
183
  return { box: "h-12 px-4", text: "text-base", spinner: "size-5" };
63
184
  } else {
64
- return { box: "h-10 px-3", text: "text-sm", spinner: "size-4" };
185
+ return { box: "h-10 px-3 pointer-coarse:min-h-11", text: "text-sm max-sm:text-base", spinner: "size-4" };
65
186
  }
66
187
  });
67
188
 
189
+ /**
190
+ * What stands inside the field at either end sits in a slot 4px in from
191
+ * the field's edge and 8px smaller than the field: 24, 32 and 40px. An
192
+ * icon is centred in it, which puts it where the text's own padding would;
193
+ * a button fills it.
194
+ */
195
+ const SLOT_INSET = 4;
196
+ const SLOTS = {
197
+ sm: { px: 24, min: "min-w-[24px]", reveal: "xs", icon: 14 },
198
+ md: { px: 32, min: "min-w-[32px]", reveal: "sm", icon: 16 },
199
+ lg: { px: 40, min: "min-w-[40px]", reveal: "md", icon: 16 },
200
+ } as const;
201
+ const slot = $derived(SLOTS[size].px);
202
+ const slotClass = $derived(SLOTS[size].min);
203
+ /** The reveal button is one size down from the field, so it fits the slot: 24, 32 and 40px. */
204
+ const revealSize = $derived(SLOTS[size].reveal);
205
+
206
+ const hasLeading = $derived(!!leading);
207
+ /** Whether the end of the field holds more than the spinner. */
208
+ const hasTrailing = $derived(!!trailing || canReveal);
209
+
210
+ /** Measured once mounted; until then, and on the server, the slots are assumed to be their least size. */
211
+ let leadingWidth = $state(0);
212
+ let trailingWidth = $state(0);
213
+
214
+ /**
215
+ * Reports the width of what stands at one end of the field, and again
216
+ * when it changes. Where there is no `ResizeObserver` (an old browser, a
217
+ * test without layout) the assumed size stays.
218
+ */
219
+ function measured(report: (width: number) => void) {
220
+ return (element: HTMLElement) => {
221
+ if (typeof ResizeObserver === "undefined") return;
222
+ const observer = new ResizeObserver(() => report(element.offsetWidth));
223
+ observer.observe(element);
224
+ return () => observer.disconnect();
225
+ };
226
+ }
227
+ const trailingCount = $derived((trailing ? 1 : 0) + (canReveal ? 1 : 0) + (loading ? 1 : 0));
228
+ const leadingPad = $derived(
229
+ hasLeading ? `${(leadingWidth || SLOT_INSET + slot) + SLOT_INSET}px` : undefined,
230
+ );
231
+ const trailingPad = $derived(
232
+ hasTrailing
233
+ ? `${(trailingWidth || SLOT_INSET + trailingCount * slot + (trailingCount - 1) * SLOT_INSET) + SLOT_INSET}px`
234
+ : undefined,
235
+ );
236
+
68
237
  const variantClass = $derived(() => {
69
- return variant === "success"
238
+ return status.variant === "success"
70
239
  ? "border-success focus-visible:border-success"
71
- : variant === "warning"
240
+ : status.variant === "warning"
72
241
  ? "border-warning focus-visible:border-warning"
73
- : variant === "error"
242
+ : status.variant === "error"
74
243
  ? "border-error focus-visible:border-error"
75
- : "border-input-border focus-visible:border-input-border";
244
+ : "border-input-border enabled:hover:border-input-border-hover focus-visible:border-input-border";
76
245
  });
77
246
 
78
247
  const inputClasses = $derived(() => {
79
248
  const sizeStyles = sizeClass();
80
- const trailingPad = loading ? (size === "sm" ? "pr-9" : "pr-10") : "";
249
+ // Room for the spinner alone. With more at the end, the measured width is used instead.
250
+ const spinnerPad = loading && !hasTrailing ? SPINNER_PAD[size] : "";
81
251
  /**
82
252
  * The field is a raised surface (`bg-input` = white in light, an inset
83
253
  * step in dark). It used to be a grey fill on a grey page, which read
@@ -85,31 +255,28 @@
85
255
  * field regardless of the layout around it.
86
256
  */
87
257
  const baseClasses =
88
- "focus-ring w-full border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-control transition-colors duration-150 placeholder:text-input-placeholder text-body focus:outline-none focus-visible:outline-none disabled:text-action-disabled-text disabled:cursor-not-allowed";
258
+ "focus-ring w-full border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-control transition-colors duration-150 placeholder:text-input-placeholder placeholder-shown:text-ellipsis text-body focus:outline-none focus-visible:outline-none disabled:text-action-disabled-text disabled:cursor-not-allowed";
259
+
260
+ // A search field that brings its own clear button in `trailing` would
261
+ // show two crosses: the browser's is taken away.
262
+ const nativeClear = trailing ? "[&::-webkit-search-cancel-button]:appearance-none" : "";
89
263
 
90
- return cn(`${baseClasses} ${sizeStyles.box} ${trailingPad} ${sizeStyles.text} ${variantClass()} ${className}`);
264
+ return cn(`${baseClasses} ${sizeStyles.box} ${spinnerPad} ${sizeStyles.text} ${variantClass()} ${nativeClear} ${className}`);
91
265
  });
92
266
 
93
267
  const labelClasses = $derived(
94
268
  () => "block text-sm font-medium text-label mb-2",
95
269
  );
96
270
 
97
- // Message text uses the `-text` step (700), which clears 4.5:1 on a page
98
- // surface; the solid fill step (600) is for fills, not for small text.
99
- const messageClasses = $derived(() => {
100
- const base = "text-sm mt-2 flex items-center gap-2";
101
- if (variant === "error") return `text-error-text ${base}`;
102
- if (variant === "success") return `text-success-text ${base}`;
103
- if (variant === "warning") return `text-warning-text ${base}`;
104
- return `text-description ${base}`;
105
- });
271
+ /**
272
+ * Both ends are laid over the field, which stays one box with one focus
273
+ * ring. They let a press through to it, except on something in them that
274
+ * can itself be pressed.
275
+ */
276
+ const SPINNER_PAD = { sm: "pe-9", md: "pe-10", lg: "pe-10" } as const;
106
277
 
107
- const getIcon = $derived(() => {
108
- if (variant === "error") return AlertCircle;
109
- if (variant === "success") return CheckCircle;
110
- if (variant === "warning") return AlertTriangle;
111
- return null;
112
- });
278
+ const END_BASE =
279
+ "pointer-events-none absolute inset-y-0 flex items-center gap-[4px] text-description [&_:is(a,button,input,select,textarea,[tabindex])]:pointer-events-auto";
113
280
 
114
281
  function handleInput(event: Event) {
115
282
  if (oninput) {
@@ -124,46 +291,85 @@
124
291
  {/if}
125
292
  <div class="relative">
126
293
  <input
294
+ bind:this={input}
127
295
  id={inputId}
128
- {type}
296
+ type={shownType}
129
297
  {name}
130
298
  bind:value
131
299
  {placeholder}
132
300
  {required}
133
301
  disabled={isDisabled}
134
302
  class={inputClasses()}
303
+ style:padding-inline-start={leadingPad}
304
+ style:padding-inline-end={trailingPad}
135
305
  oninput={handleInput}
136
306
  {onblur}
137
- aria-invalid={variant === "error" ? "true" : undefined}
307
+ aria-invalid={status.variant === "error" ? "true" : undefined}
138
308
  aria-required={required ? "true" : undefined}
139
309
  aria-busy={loading ? "true" : undefined}
140
- aria-describedby={message ? `${inputId}-message` : undefined}
310
+ aria-describedby={fieldDescribedBy(inputId, status, describedBy)}
141
311
  {...restProps}
142
312
  />
143
- {#if loading}
313
+ {#if leading}
144
314
  <span
145
- class="pointer-events-none absolute inset-y-0 right-3 flex items-center text-description"
146
- aria-hidden="true"
315
+ class={cn(END_BASE, "start-0 ps-[4px]")}
316
+ {@attach measured((width) => (leadingWidth = width))}
317
+ data-input-leading
147
318
  >
148
- <span
149
- class="inline-block {sizeClass()
150
- .spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70"
151
- ></span>
319
+ <span class={cn("flex items-center justify-center", slotClass)}>
320
+ {@render leading()}
321
+ </span>
322
+ </span>
323
+ {/if}
324
+ {#if loading || hasTrailing}
325
+ <span
326
+ class={cn(END_BASE, "end-0 pe-[4px]")}
327
+ {@attach measured((width) => (trailingWidth = width))}
328
+ data-input-trailing
329
+ >
330
+ {#if trailing}
331
+ <span class={cn("flex items-center justify-center", slotClass)}>
332
+ {@render trailing()}
333
+ </span>
334
+ {/if}
335
+ {#if canReveal}
336
+ <!-- A toggle with one name: "Show password", pressed or not.
337
+ A name that changed with the state would read as "Hide
338
+ password, pressed". The visible box is 4px inside the field
339
+ at every size, so its corners are the field's less 4px; on a
340
+ touch screen the layer around it is the 44px target. -->
341
+ <IconButton
342
+ variant="ghost"
343
+ size={revealSize}
344
+ bind:pressed={revealed}
345
+ label={revealLabel}
346
+ disabled={isDisabled}
347
+ class={cn(
348
+ "rounded-[calc(var(--radius-control)-4px)] pointer-coarse:relative pointer-coarse:min-h-0 pointer-coarse:min-w-0",
349
+ TOUCH_HIT_AREA,
350
+ )}
351
+ onmousedown={keepFocus}
352
+ onclick={restoreCaret}
353
+ data-input-reveal
354
+ >
355
+ {#if revealed}
356
+ <EyeOff size={SLOTS[size].icon} aria-hidden="true" />
357
+ {:else}
358
+ <Eye size={SLOTS[size].icon} aria-hidden="true" />
359
+ {/if}
360
+ </IconButton>
361
+ {/if}
362
+ {#if loading}
363
+ <!-- 12px from the field's edge, where it has always been. -->
364
+ <span class="flex items-center ps-[4px] pe-[8px]" aria-hidden="true">
365
+ <span
366
+ class="inline-block {sizeClass()
367
+ .spinner} shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70 motion-reduce:animate-pulse"
368
+ ></span>
369
+ </span>
370
+ {/if}
152
371
  </span>
153
372
  {/if}
154
373
  </div>
155
- {#if message && variant !== "default"}
156
- <p
157
- id={`${inputId}-message`}
158
- class={messageClasses()}
159
- role={variant === "error" ? "alert" : "status"}
160
- aria-live={variant === "error" ? "assertive" : "polite"}
161
- >
162
- {#if getIcon()}
163
- {@const Icon = getIcon()}
164
- <Icon size={14} class="shrink-0" />
165
- {/if}
166
- <span>{message}</span>
167
- </p>
168
- {/if}
374
+ <FieldMessages fieldId={inputId} {status} {hint} />
169
375
  </div>
@@ -1,5 +1,11 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLInputAttributes } from "svelte/elements";
1
3
  import type { SemanticVariant, SizeVariant } from "../types/variants.js";
2
- interface Props {
4
+ /**
5
+ * Other attributes (`autocomplete`, `inputmode`, `maxlength`,
6
+ * `enterkeyhint`, `data-*`, `onchange`, ...) land on the `<input>`.
7
+ */
8
+ type Props = Omit<HTMLInputAttributes, "class" | "size" | "value" | "type" | "id" | "name" | "placeholder" | "required" | "disabled" | "oninput" | "onblur" | "aria-label" | "aria-describedby"> & {
3
9
  /** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
4
10
  id?: string;
5
11
  value?: string;
@@ -15,11 +21,36 @@ interface Props {
15
21
  loading?: boolean;
16
22
  size?: SizeVariant;
17
23
  variant?: SemanticVariant;
24
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
18
25
  message?: string;
26
+ /** Help text under the field, read out with it. */
27
+ hint?: string;
28
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
29
+ error?: string;
30
+ /**
31
+ * Drawn inside the field, before the text: an icon, a unit. Decoration
32
+ * lets a press through to the field; a button or link in it is pressable.
33
+ */
34
+ leading?: Snippet;
35
+ /** Drawn inside the field, after the text and before the loading spinner's place. */
36
+ trailing?: Snippet;
37
+ /**
38
+ * With `type="password"`: a button at the end of the field that shows
39
+ * the password as text, and hides it again. On by default; `false`
40
+ * leaves it out. Ignored for other types.
41
+ */
42
+ revealable?: boolean;
43
+ /**
44
+ * Accessible name of that button. It stays the same in both states;
45
+ * the button's pressed state says whether the password is showing.
46
+ */
47
+ revealLabel?: string;
19
48
  oninput?: (event: Event) => void;
20
49
  onblur?: (event: Event) => void;
21
50
  "aria-label"?: string;
22
- }
51
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
52
+ "aria-describedby"?: string | null;
53
+ };
23
54
  declare const Input: import("svelte").Component<Props, {}, "value">;
24
55
  type Input = ReturnType<typeof Input>;
25
56
  export default Input;
@@ -32,7 +32,11 @@
32
32
  const className = $derived(cn(`${classAttr} ${legacyClass}`));
33
33
 
34
34
  const listClasses = $derived(
35
- cn(`space-y-1 overflow-hidden rounded-container ${className}`),
35
+ // The same radius as its rows. Not `overflow-hidden`: the rows fill
36
+ // the list edge to edge, so clipping here cut a row's focus ring off
37
+ // on every side but the gap to the next row. `list-dividers` draws the
38
+ // hairline in the gap between rows (app.css).
39
+ cn(`list-dividers space-y-1 rounded-[var(--zabi-list-row-radius,var(--radius-container))] ${className}`),
36
40
  );
37
41
  </script>
38
42
 
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { ArrowRight } from "@lucide/svelte";
2
+ import ArrowRight from "@lucide/svelte/icons/arrow-right";
3
3
  import type { Component, Snippet } from "svelte";
4
4
  import ListItemLeading from "./ListItemLeading.svelte";
5
5
 
@@ -13,6 +13,12 @@
13
13
  avatar?: string;
14
14
  /** `avatar` alt text; `""` if decorative. */
15
15
  avatarAlt?: string;
16
+ /**
17
+ * Leading picture drawn in the round, in place of an address: an
18
+ * inline SVG. Wins over `avatar` and `icon`. Decorative unless
19
+ * `avatarAlt` is set, which then names it.
20
+ */
21
+ avatarPicture?: Snippet;
16
22
  target?: "_self" | "_blank" | "_parent" | "_top";
17
23
  rel?: string;
18
24
  disabled?: boolean;
@@ -21,9 +27,14 @@
21
27
  interface Props {
22
28
  item: ListItemData;
23
29
  selected?: boolean;
30
+ /** The arrow at the end of a row that can be pressed. A row with nothing to do never has one. */
24
31
  showArrow?: boolean;
25
32
  /** Shown before the chevron when `showArrow` is true. */
26
33
  trailing?: Snippet;
34
+ /**
35
+ * Called when the row is pressed. A row with neither this nor
36
+ * `item.href` has nothing to do: it is plain content, not a button.
37
+ */
27
38
  onclick?: (item: ListItemData, event: MouseEvent) => void;
28
39
  }
29
40
 
@@ -36,16 +47,42 @@
36
47
  ...restProps
37
48
  }: Props = $props();
38
49
 
50
+ /**
51
+ * A row is a link with `item.href`, a button with `onclick`, and otherwise
52
+ * plain content: it used to be a focusable button with a pointer cursor
53
+ * and an arrow, and nothing happened when it was pressed.
54
+ */
55
+ const interactive = $derived(!!item.href || !!onclick);
56
+
39
57
  const itemClasses = $derived.by(() => {
40
58
  const baseClasses =
41
- "group focus-ring flex w-full items-center gap-3 rounded-container border border-border px-4 py-3 pr-5 text-left transition-all duration-150";
42
- const interactiveClasses = item.disabled
43
- ? "cursor-not-allowed opacity-50"
44
- : "cursor-pointer focus-visible:bg-surface-hover";
45
- const selectedClasses = selected
46
- ? "bg-action-primary-subtle border-brand-500"
47
- : "";
48
- return `${baseClasses} ${interactiveClasses} ${selectedClasses}`.trim();
59
+ // The container radius, or the one a `.list-group` shell around the list
60
+ // hands down so the row is concentric with it (see app.css).
61
+ // The 1px border is kept so the row's height and text do not move, but
62
+ // it is transparent: rows are told apart by the divider between them
63
+ // (app.css), and a bordered row inside a bordered shell was two
64
+ // outlines. `--zabi-list-row-border-color` puts the 8.1 edge back.
65
+ "group flex w-full items-center gap-3 rounded-[var(--zabi-list-row-radius,var(--radius-container))] border px-4 py-3 pr-5 text-left transition-colors duration-(--duration-base)";
66
+ const edge = "border-[color:var(--zabi-list-row-border-color,transparent)]";
67
+ if (!interactive) {
68
+ const tone = selected ? "bg-action-primary-subtle border-action-primary" : edge;
69
+ return `${baseClasses} ${tone} ${item.disabled ? "opacity-50" : ""}`.trim();
70
+ }
71
+ const cursorClasses = item.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer";
72
+ // A selected row keeps its fill under keyboard focus, which the focus
73
+ // ring already shows; the hover tint is for rows with no fill. The two
74
+ // never share an element, and neither does the selected border with
75
+ // `border-border`: beside each other the second of each pair lost.
76
+ //
77
+ // Hover is for a pointer that can hover (the hand-written rule is
78
+ // gated on it, so the tint does not stay on after a tap); the pressed
79
+ // fill is for every pointer, and is what a finger sees.
80
+ const stateClasses = selected
81
+ ? "bg-action-primary-subtle border-action-primary active:bg-action-primary-subtle-active"
82
+ : item.disabled
83
+ ? edge
84
+ : `${edge} hover:bg-surface-hover focus-visible:bg-surface-hover active:bg-surface-active`;
85
+ return `focus-ring ${baseClasses} ${cursorClasses} ${stateClasses}`.trim();
49
86
  });
50
87
 
51
88
  const ariaCurrent = $derived(item.href && selected ? "page" : undefined);
@@ -70,7 +107,18 @@
70
107
  </script>
71
108
 
72
109
  {#snippet rowContent()}
73
- {#if item.avatar}
110
+ {#if item.avatarPicture}
111
+ <ListItemLeading class="rounded-full">
112
+ <span
113
+ class="flex size-full items-center justify-center"
114
+ role={item.avatarAlt ? "img" : undefined}
115
+ aria-label={item.avatarAlt || undefined}
116
+ aria-hidden={item.avatarAlt ? undefined : "true"}
117
+ >
118
+ {@render item.avatarPicture()}
119
+ </span>
120
+ </ListItemLeading>
121
+ {:else if item.avatar}
74
122
  <ListItemLeading class="rounded-full">
75
123
  <img
76
124
  src={item.avatar}
@@ -101,10 +149,10 @@
101
149
  {@render trailing()}
102
150
  </span>
103
151
  {/if}
104
- {#if showArrow}
152
+ {#if showArrow && interactive}
105
153
  <ArrowRight
106
154
  size={16}
107
- class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-1 group-focus-visible:translate-x-1"
155
+ class="shrink-0 text-description transition-transform duration-(--duration-base) group-hover:translate-x-1 group-focus-visible:translate-x-1"
108
156
  aria-hidden="true"
109
157
  />
110
158
  {/if}
@@ -119,19 +167,28 @@
119
167
  aria-disabled={item.disabled ? "true" : undefined}
120
168
  tabindex={item.disabled ? -1 : undefined}
121
169
  class={itemClasses}
170
+ data-selected={selected ? "true" : undefined}
122
171
  onclick={handleItemClick}
123
172
  {...restProps}
124
173
  >
125
174
  {@render rowContent()}
126
175
  </a>
127
- {:else}
176
+ {:else if onclick}
128
177
  <button
129
178
  type="button"
130
179
  class={itemClasses}
180
+ data-selected={selected ? "true" : undefined}
131
181
  disabled={item.disabled}
132
182
  onclick={handleItemClick}
133
183
  {...restProps}
134
184
  >
135
185
  {@render rowContent()}
136
186
  </button>
187
+ {:else}
188
+ <!-- A plain row has no role, so `aria-disabled` on it would say nothing
189
+ to anyone: a disabled one is only drawn dimmed. There is nothing in it to
190
+ press, enabled or not. -->
191
+ <div class={itemClasses} data-selected={selected ? "true" : undefined} {...restProps}>
192
+ {@render rowContent()}
193
+ </div>
137
194
  {/if}
@@ -12,6 +12,12 @@ export interface ListItemData {
12
12
  avatar?: string;
13
13
  /** `avatar` alt text; `""` if decorative. */
14
14
  avatarAlt?: string;
15
+ /**
16
+ * Leading picture drawn in the round, in place of an address: an
17
+ * inline SVG. Wins over `avatar` and `icon`. Decorative unless
18
+ * `avatarAlt` is set, which then names it.
19
+ */
20
+ avatarPicture?: Snippet;
15
21
  target?: "_self" | "_blank" | "_parent" | "_top";
16
22
  rel?: string;
17
23
  disabled?: boolean;
@@ -19,9 +25,14 @@ export interface ListItemData {
19
25
  interface Props {
20
26
  item: ListItemData;
21
27
  selected?: boolean;
28
+ /** The arrow at the end of a row that can be pressed. A row with nothing to do never has one. */
22
29
  showArrow?: boolean;
23
30
  /** Shown before the chevron when `showArrow` is true. */
24
31
  trailing?: Snippet;
32
+ /**
33
+ * Called when the row is pressed. A row with neither this nor
34
+ * `item.href` has nothing to do: it is plain content, not a button.
35
+ */
25
36
  onclick?: (item: ListItemData, event: MouseEvent) => void;
26
37
  }
27
38
  declare const ListItem: Component<Props, {}, "">;