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,16 +1,62 @@
1
- interface Props {
1
+ import type { HTMLButtonAttributes } from "svelte/elements";
2
+ import { type SelectPresentation, type SelectStrings } from "../util/select.js";
3
+ /**
4
+ * Other attributes (`data-*`, `aria-*`, `onfocus`, ...) land on the
5
+ * trigger, which is a `<button>`: it is the control that takes focus and
6
+ * that the label names.
7
+ *
8
+ * The server's HTML also carries a native `<select>` with the same
9
+ * options. Until the component has mounted, and for good where there are
10
+ * no scripts, that is the control: it is what is seen, pressed and
11
+ * submitted. Once mounted the trigger and the list take over, and the
12
+ * native select stays, out of sight and out of reach, as the form control
13
+ * (`name`, `required`), kept at the same value. With
14
+ * `presentation="native"` it is the control throughout.
15
+ */
16
+ type Props = Omit<HTMLButtonAttributes, "class" | "value" | "name" | "disabled" | "type" | "onchange" | "onclick" | "aria-describedby" | "children"> & {
2
17
  /** Extra classes for the host element. */
3
18
  class?: string;
19
+ /** Id of the trigger. Omit to auto-generate. */
20
+ id?: string;
4
21
  value?: string | number | undefined;
5
22
  options?: Array<{
6
23
  value: string | number;
7
24
  label: string;
8
25
  disabled?: boolean;
26
+ /**
27
+ * The language of the label, as a BCP 47 tag (`"en"`), when it is
28
+ * not the page's (WCAG 3.1.2). Set on the option's row, on the
29
+ * trigger's text while it is chosen, and on the native `<option>`.
30
+ */
31
+ lang?: string;
9
32
  }>;
10
33
  searchable?: boolean;
34
+ /**
35
+ * The component's own texts, for another language. Whatever is left
36
+ * out keeps its English default. The six older props for single texts
37
+ * (`placeholder`, `searchPlaceholder`, `noResultsText`,
38
+ * `loadingText`, `emptyStateTitle`, `emptyStateDescription`) win
39
+ * over it.
40
+ */
41
+ strings?: Partial<SelectStrings>;
11
42
  searchPlaceholder?: string;
12
43
  maxMenuHeight?: string;
13
44
  menuWidth?: string;
45
+ /**
46
+ * How the list is shown. `auto`: in a BottomSheet on a phone (a touch
47
+ * screen narrower than 640px), under the trigger everywhere else.
48
+ * `popover` and `sheet` are always the one or the other. `native`:
49
+ * only the browser's own `<select>`, styled as the field, with the
50
+ * platform's picker. It shows the options' labels and nothing else:
51
+ * no search field, and none of the loading or empty states.
52
+ */
53
+ presentation?: SelectPresentation;
54
+ /**
55
+ * In a sheet, a short list (up to six options) without a search field
56
+ * gets a sheet as tall as the list, not half the screen. `false`
57
+ * keeps the half-height sheet.
58
+ */
59
+ sheetFit?: boolean;
14
60
  noResultsText?: string;
15
61
  isLoading?: boolean;
16
62
  loadingText?: string;
@@ -19,16 +65,28 @@ interface Props {
19
65
  emptyStateActionLabel?: string;
20
66
  placeholder?: string;
21
67
  label?: string;
22
- /** When set, a hidden input submits the selected value with native forms. */
68
+ /** The name the chosen value is submitted under, by the native `<select>`. */
23
69
  name?: string;
70
+ /**
71
+ * The form is not sent while nothing is chosen. Checked by the
72
+ * browser on the native `<select>`; once mounted, the browser's own
73
+ * message is shown under the field and focus goes to the trigger.
74
+ */
24
75
  required?: boolean;
25
76
  disabled?: boolean;
26
77
  size?: "sm" | "md" | "lg";
27
78
  variant?: "default" | "success" | "warning" | "error";
79
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
28
80
  message?: string;
81
+ /** Help text under the field, read out with it. */
82
+ hint?: string;
83
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
84
+ error?: string;
29
85
  onchange?: (event: Event) => void;
30
86
  onEmptyStateAction?: () => void;
31
- }
87
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
88
+ "aria-describedby"?: string | null;
89
+ };
32
90
  declare const Select: import("svelte").Component<Props, {}, "value">;
33
91
  type Select = ReturnType<typeof Select>;
34
92
  export default Select;
@@ -1,8 +1,14 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from "svelte";
2
+ import { onMount, tick, type Snippet } from "svelte";
3
+ import type { HTMLInputAttributes } from "svelte/elements";
3
4
  import { generateId } from "../util/ssr-safe.js";
4
5
  import { cn } from "../util/cn.js";
6
+ import { groupBinding } from "../util/hydration.js";
7
+ import { fieldDescribedBy, fieldMessageState } from "../util/field.js";
8
+ import type { SemanticVariant } from "../types/variants.js";
9
+ import FieldMessages from "./FieldMessages.svelte";
5
10
  import {
11
+ SELECTION_CONTROL_INPUT,
6
12
  SELECTION_CONTROL_LABEL_ROW,
7
13
  selectionControlRingOverlayClasses,
8
14
  selectionControlShellClasses,
@@ -17,7 +23,10 @@
17
23
  disabled: boolean;
18
24
  };
19
25
 
20
- interface Props {
26
+ type Props = Omit<
27
+ HTMLInputAttributes,
28
+ "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"
29
+ > & {
21
30
  /** Extra classes for the host element. */
22
31
  class?: string;
23
32
  id?: string;
@@ -33,7 +42,31 @@
33
42
  onChange?: (event: Event) => void;
34
43
  onchange?: (event: Event) => void;
35
44
  mark?: Snippet<[SelectionControlMarkProps]>;
36
- }
45
+ /**
46
+ * Drawn inside the one label, between the control and the label text,
47
+ * as decoration for the name `label` already gives. A leading image
48
+ * should have an empty alt: the label names the row.
49
+ */
50
+ leading?: Snippet;
51
+ /**
52
+ * A line under the label, tied to the input with `aria-describedby`.
53
+ * It is not part of the name: the label names the control.
54
+ */
55
+ description?: string;
56
+ /**
57
+ * Where the control sits in the row. `end` puts it after the leading,
58
+ * the label and the description, at the far edge; the row is still
59
+ * one label and fills the width it is given.
60
+ */
61
+ controlPosition?: "start" | "end";
62
+ variant?: SemanticVariant;
63
+ /** Status text under the control. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
64
+ message?: string;
65
+ /** Help text under the control, read out with it. */
66
+ hint?: string;
67
+ /** An error under the control. It marks the input invalid and is announced; it wins over `variant` and `message`. */
68
+ error?: string;
69
+ };
37
70
 
38
71
  let {
39
72
  class: className = "",
@@ -46,26 +79,111 @@
46
79
  disabled = false,
47
80
  loading = false,
48
81
  defaultChecked = false,
49
- checked = $bindable(defaultChecked),
82
+ checked = $bindable<Exclude<Props["checked"], undefined>>(),
50
83
  onChange,
51
84
  onchange,
52
85
  mark,
86
+ leading,
87
+ description,
88
+ controlPosition = "start",
89
+ variant = "default",
90
+ message = "",
91
+ hint = "",
92
+ error = "",
53
93
  ...restProps
54
94
  }: Props = $props();
55
95
 
96
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
97
+ // one that has a fallback (`props_invalid_value`), and a page that throws
98
+ // while it hydrates never becomes interactive. The default is applied
99
+ // here instead: at once, for the server and the first render, and again
100
+ // whenever a parent hands back `undefined`.
101
+ const applyDefaults = () => {
102
+ if (checked === undefined) checked = defaultChecked;
103
+ };
104
+ applyDefaults();
105
+ $effect.pre(applyDefaults);
106
+
56
107
  /** Call once per mount — `generateId()` in `$derived` would churn ids every tick. */
57
108
  const fallbackId = generateId("selection-control");
58
109
  const controlId = $derived(idProp ?? fallbackId);
59
110
  const isDisabled = $derived(disabled || loading);
60
111
 
61
- const labelWrapperClasses = SELECTION_CONTROL_LABEL_ROW;
112
+ const descriptionId = $derived(`${controlId}-description`);
113
+ const status = $derived(fieldMessageState({ variant, message, hint, error }));
114
+ const hasFieldText = $derived(status.showMessage || status.showHint);
115
+ // The caller's ids, then the description, the hint and the message.
116
+ const describedBy = $derived(
117
+ fieldDescribedBy(
118
+ controlId,
119
+ status,
120
+ [restProps["aria-describedby"], description ? descriptionId : undefined]
121
+ .filter(Boolean)
122
+ .join(" "),
123
+ ),
124
+ );
125
+ // `aria-invalid` is not supported by the radio role: a radio's error is said by its description.
126
+ const invalid = $derived(status.variant === "error" ? "true" : undefined);
62
127
 
63
- const controlContainerClasses = $derived(selectionControlShellClasses(shape));
128
+ // With a description the row is two lines: the box sits at the top, as in RadioGroup.
129
+ // At the end the control is moved by `order`, so the markup and what a
130
+ // screen reader meets first are the same in both positions.
131
+ const controlAtEnd = $derived(controlPosition === "end");
132
+ const labelWrapperClasses = $derived(
133
+ cn(SELECTION_CONTROL_LABEL_ROW, description && "items-start", controlAtEnd && "min-w-0 flex-1"),
134
+ );
135
+
136
+ const controlContainerClasses = $derived(
137
+ cn(selectionControlShellClasses(shape), description && "mt-0.5", controlAtEnd && "order-last"),
138
+ );
139
+ const labelTextClasses = $derived(controlAtEnd ? "min-w-0 flex-1" : "");
64
140
 
65
141
  const controlRingClasses = $derived(selectionControlRingOverlayClasses(shape));
66
142
 
143
+ let input: HTMLInputElement | undefined = $state();
144
+
145
+ /**
146
+ * The input is bound, so a tick made before the page hydrated is kept:
147
+ * Svelte's binding finds the input changed and hands its state over here
148
+ * instead of overwriting it (see util/hydration.ts). A form reset arrives
149
+ * the same way.
150
+ *
151
+ * A choice that is adopted like that came without a `change` event that
152
+ * anything heard, and a parent that only listens (`onchange`, no
153
+ * `bind:checked`) would never learn of it. So while the component
154
+ * settles, a change made through the binding is noted, and reported once
155
+ * with a real `change` event on the input: the callbacks get the event
156
+ * they always get, and a form around the control hears it too.
157
+ */
158
+ let settling = true;
159
+ let adopted = false;
160
+
161
+ function take(next: boolean) {
162
+ if (next === !!checked) return;
163
+ checked = next;
164
+ if (settling) adopted = true;
165
+ }
166
+
167
+ /** What a single radio's group is bound to: its own value while it is checked. */
168
+ const NOT_CHOSEN = {};
169
+ const radio = groupBinding<unknown>(
170
+ () => (checked ? value : NOT_CHOSEN),
171
+ (chosen) => take(chosen === value),
172
+ () => take(false),
173
+ );
174
+
175
+ onMount(() => {
176
+ // The bindings adopt in a microtask of their own, queued before this one.
177
+ void tick().then(() => {
178
+ settling = false;
179
+ if (adopted) input?.dispatchEvent(new Event("change", { bubbles: true }));
180
+ });
181
+ });
182
+
67
183
  function handleChange(event: Event) {
68
184
  if (isDisabled) return;
185
+ // A real change is its own report: nothing is left to tell.
186
+ adopted = false;
69
187
  const target = event.target as HTMLInputElement;
70
188
  checked = target.checked;
71
189
  onChange?.(event);
@@ -73,30 +191,82 @@
73
191
  }
74
192
  </script>
75
193
 
76
- <div class={cn("flex items-center gap-2", className)}>
194
+ {#snippet row()}
77
195
  <label for={controlId} class={labelWrapperClasses}>
78
196
  <span class={controlContainerClasses}>
79
- <input
80
- {type}
81
- id={controlId}
82
- {name}
83
- {value}
84
- {checked}
85
- disabled={isDisabled}
86
- class="sr-only"
87
- aria-busy={loading ? "true" : undefined}
88
- onchange={handleChange}
89
- {...restProps}
90
- />
197
+ <!-- One branch per type: a binding needs a type it can read. -->
198
+ {#if type === "checkbox"}
199
+ <input
200
+ bind:this={input}
201
+ type="checkbox"
202
+ id={controlId}
203
+ {name}
204
+ {value}
205
+ bind:checked={() => !!checked, take}
206
+ disabled={isDisabled}
207
+ class={SELECTION_CONTROL_INPUT}
208
+ aria-busy={loading ? "true" : undefined}
209
+ onchange={handleChange}
210
+ {...restProps}
211
+ aria-describedby={describedBy}
212
+ aria-invalid={invalid ?? restProps["aria-invalid"]}
213
+ />
214
+ {:else}
215
+ <input
216
+ bind:this={input}
217
+ type="radio"
218
+ id={controlId}
219
+ {name}
220
+ {value}
221
+ bind:group={radio.value}
222
+ disabled={isDisabled}
223
+ class={SELECTION_CONTROL_INPUT}
224
+ aria-busy={loading ? "true" : undefined}
225
+ onchange={handleChange}
226
+ {...restProps}
227
+ aria-describedby={describedBy}
228
+ />
229
+ {/if}
91
230
  <span class={controlRingClasses}></span>
92
231
  {#if mark}
93
- {@render mark({ checked, loading, disabled: isDisabled })}
232
+ {@render mark({ checked: !!checked, loading, disabled: isDisabled })}
94
233
  {/if}
95
234
  </span>
96
- {#if label}
97
- <span class="text-sm font-medium text-label">
235
+ {#if leading}
236
+ <span class="inline-flex shrink-0 items-center">
237
+ {@render leading()}
238
+ </span>
239
+ {/if}
240
+ {#if description}
241
+ <span class={cn("flex flex-col gap-1", labelTextClasses)}>
242
+ <span class="text-sm font-medium text-label">
243
+ {label}
244
+ </span>
245
+ <!-- aria-hidden keeps it out of the name; aria-describedby still reads it. -->
246
+ <span id={descriptionId} class="text-sm text-description" aria-hidden="true">
247
+ {description}
248
+ </span>
249
+ </span>
250
+ {:else if label}
251
+ <span class={cn("text-sm font-medium text-label", labelTextClasses)}>
98
252
  {label}
99
253
  </span>
100
254
  {/if}
101
255
  </label>
102
- </div>
256
+ {/snippet}
257
+
258
+ {#if hasFieldText}
259
+ <div class={cn("flex flex-col", className)}>
260
+ <div class="flex items-center gap-2">
261
+ {@render row()}
262
+ </div>
263
+ <!-- Under the label text: the box is 20px and the gap 8px. With the box at the end, from the row's edge. -->
264
+ <div class={controlAtEnd ? "" : "ps-7"}>
265
+ <FieldMessages fieldId={controlId} {status} {hint} gap="mt-1" />
266
+ </div>
267
+ </div>
268
+ {:else}
269
+ <div class={cn("flex items-center gap-2", className)}>
270
+ {@render row()}
271
+ </div>
272
+ {/if}
@@ -1,4 +1,6 @@
1
- import type { Snippet } from "svelte";
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { SemanticVariant } from "../types/variants.js";
2
4
  import { type SelectionControlShape } from "./selection-control.styles";
3
5
  type SelectionControlType = "checkbox" | "radio";
4
6
  export type SelectionControlMarkProps = {
@@ -6,7 +8,7 @@ export type SelectionControlMarkProps = {
6
8
  loading: boolean;
7
9
  disabled: boolean;
8
10
  };
9
- interface Props {
11
+ type Props = Omit<HTMLInputAttributes, "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"> & {
10
12
  /** Extra classes for the host element. */
11
13
  class?: string;
12
14
  id?: string;
@@ -22,7 +24,31 @@ interface Props {
22
24
  onChange?: (event: Event) => void;
23
25
  onchange?: (event: Event) => void;
24
26
  mark?: Snippet<[SelectionControlMarkProps]>;
25
- }
27
+ /**
28
+ * Drawn inside the one label, between the control and the label text,
29
+ * as decoration for the name `label` already gives. A leading image
30
+ * should have an empty alt: the label names the row.
31
+ */
32
+ leading?: Snippet;
33
+ /**
34
+ * A line under the label, tied to the input with `aria-describedby`.
35
+ * It is not part of the name: the label names the control.
36
+ */
37
+ description?: string;
38
+ /**
39
+ * Where the control sits in the row. `end` puts it after the leading,
40
+ * the label and the description, at the far edge; the row is still
41
+ * one label and fills the width it is given.
42
+ */
43
+ controlPosition?: "start" | "end";
44
+ variant?: SemanticVariant;
45
+ /** Status text under the control. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
46
+ message?: string;
47
+ /** Help text under the control, read out with it. */
48
+ hint?: string;
49
+ /** An error under the control. It marks the input invalid and is announced; it wins over `variant` and `message`. */
50
+ error?: string;
51
+ };
26
52
  declare const SelectionControl: import("svelte").Component<Props, {}, "checked">;
27
53
  type SelectionControl = ReturnType<typeof SelectionControl>;
28
54
  export default SelectionControl;
@@ -1,56 +1,152 @@
1
1
  <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+
2
4
  import { cn } from "../util/cn.js";
5
+ import { mergeStrings } from "../util/ready-made-strings.js";
6
+ import {
7
+ DEFAULT_SKELETON_STRINGS,
8
+ inSkeletonGroup,
9
+ skeletonLineCount,
10
+ type SkeletonStrings,
11
+ } from "../util/skeleton.js";
12
+ import { zabiStringsFor } from "../util/zabi-strings.js";
3
13
 
4
14
  type Variant = "text" | "circle" | "block";
15
+ type Tone = "neutral" | "on-color";
5
16
 
6
- interface Props {
17
+ /**
18
+ * Anything else a `div` takes (`id`, `data-testid`, `style`, `aria-*`) goes to the piece, or to the
19
+ * paragraph's wrapper with `lines`. `role`, `aria-busy`, `aria-hidden` and `data-skeleton` are set by
20
+ * the component and are not overridden.
21
+ */
22
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy" | "aria-hidden"> & {
7
23
  variant?: Variant;
8
24
  /** CSS length or px number; omit to use variant default (circle stays square). */
9
25
  width?: string | number;
10
- /** CSS length or px number; omit for variant default height. */
26
+ /** CSS length or px number; omit for variant default height. With `lines`, the height of each line. */
11
27
  height?: string | number;
12
28
  class?: string;
29
+ /** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
13
30
  "aria-label"?: string;
14
- }
31
+ /** Overrides for the built-in strings. */
32
+ strings?: Partial<SkeletonStrings>;
33
+ /**
34
+ * Draws the piece only: no role, no label, hidden from assistive tech.
35
+ * For a piece beside a status of your own. A piece under a `SkeletonGroup` is decorative already.
36
+ */
37
+ decorative?: boolean;
38
+ /**
39
+ * "neutral" is the quiet fill for a page or a card. "on-color" is the label colour of
40
+ * the surface it sits on at a low strength, for a brand, accent or fill block (`on-brand`,
41
+ * `on-accent`, `on-fill`, a `Card` with `tone`), in light and dark.
42
+ */
43
+ tone?: Tone;
44
+ /**
45
+ * Text variant only: draws this many lines (1 to 20) as one placeholder, the last one shorter.
46
+ * `class` and `width` then apply to the whole paragraph, `height` to each line.
47
+ */
48
+ lines?: number;
49
+ };
15
50
 
16
51
  let {
17
52
  variant = "text",
18
53
  width,
19
54
  height,
20
55
  class: className = "",
21
- "aria-label": ariaLabel = "Loading…",
56
+ "aria-label": ariaLabel,
57
+ strings,
58
+ decorative = false,
59
+ tone = "neutral",
60
+ lines,
61
+ style,
62
+ ...restProps
22
63
  }: Props = $props();
23
64
 
65
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
66
+ const provided = zabiStringsFor("skeleton");
67
+ const text = $derived(mergeStrings(DEFAULT_SKELETON_STRINGS, provided(), strings));
68
+
69
+ const grouped = inSkeletonGroup();
70
+ const hidden = $derived(decorative || grouped);
71
+
24
72
  const radius: Record<Variant, string> = {
25
73
  text: "rounded-control",
26
74
  circle: "rounded-full",
27
75
  block: "rounded-control",
28
76
  };
29
77
 
78
+ /**
79
+ * The variant's own box, at zero specificity (`:where()`): any size class at
80
+ * the call site wins over it in the stylesheet, whether `cn()` knows its name
81
+ * (`size-14`) or not (`size-sticker`, an app's own step).
82
+ */
30
83
  const defaultShape: Record<Variant, string> = {
31
- text: "w-full h-3",
32
- circle: "w-10 aspect-square",
33
- block: "w-full h-32",
84
+ text: "[:where(&)]:w-full [:where(&)]:h-3",
85
+ circle: "[:where(&)]:w-10 aspect-square",
86
+ block: "[:where(&)]:w-full [:where(&)]:h-32",
87
+ };
88
+
89
+ const fill: Record<Tone, string> = {
90
+ neutral: "bg-neutral-subtle",
91
+ "on-color": "bg-skeleton-on-color",
34
92
  };
35
93
 
36
94
  function toSize(value: string | number): string {
37
95
  return typeof value === "number" ? `${value}px` : value;
38
96
  }
39
97
 
98
+ /** The component's own inline styles first, then the caller's `style`, which wins where both set a property. */
99
+ function joinStyles(...parts: Array<string | null | undefined>): string | undefined {
100
+ const joined = parts
101
+ .filter(Boolean)
102
+ .map((part) => String(part).replace(/;\s*$/, ""))
103
+ .join("; ");
104
+ return joined || undefined;
105
+ }
106
+
40
107
  const inlineStyle = $derived(
41
- [
108
+ joinStyles(
42
109
  width !== undefined ? `width: ${toSize(width)}` : null,
43
110
  height !== undefined ? `height: ${toSize(height)}` : null,
44
- ]
45
- .filter(Boolean)
46
- .join("; "),
111
+ style,
112
+ ),
47
113
  );
114
+
115
+ const lineCount = $derived(variant === "text" ? skeletonLineCount(lines) : 0);
116
+ const lineStyle = $derived(height !== undefined ? `height: ${toSize(height)}` : undefined);
117
+ const wrapperStyle = $derived(joinStyles(width !== undefined ? `width: ${toSize(width)}` : null, style));
118
+ const pieceClass = $derived(cn("animate-pulse motion-reduce:animate-none", fill[tone], radius.text));
48
119
  </script>
49
120
 
50
- <div
51
- class={cn("animate-pulse bg-surface-2", defaultShape[variant], radius[variant], className)}
52
- style={inlineStyle || undefined}
53
- role="status"
54
- aria-busy="true"
55
- aria-label={ariaLabel}
56
- ></div>
121
+ {#if lineCount > 0}
122
+ <div
123
+ {...restProps}
124
+ class={cn("flex w-full flex-col gap-2", className)}
125
+ style={wrapperStyle}
126
+ data-skeleton-paragraph
127
+ role={hidden ? undefined : "status"}
128
+ aria-busy={hidden ? undefined : "true"}
129
+ aria-label={hidden ? undefined : (ariaLabel ?? text.label)}
130
+ aria-hidden={hidden ? "true" : undefined}
131
+ >
132
+ {#each { length: lineCount } as _, index (index)}
133
+ <div
134
+ class={cn(pieceClass, "h-3", index === lineCount - 1 && lineCount > 1 ? "w-3/5" : "w-full")}
135
+ style={lineStyle}
136
+ data-skeleton="text"
137
+ data-skeleton-line
138
+ ></div>
139
+ {/each}
140
+ </div>
141
+ {:else}
142
+ <div
143
+ {...restProps}
144
+ class={cn("animate-pulse motion-reduce:animate-none", fill[tone], defaultShape[variant], radius[variant], className)}
145
+ style={inlineStyle}
146
+ data-skeleton={variant}
147
+ role={hidden ? undefined : "status"}
148
+ aria-busy={hidden ? undefined : "true"}
149
+ aria-label={hidden ? undefined : (ariaLabel ?? text.label)}
150
+ aria-hidden={hidden ? "true" : undefined}
151
+ ></div>
152
+ {/if}
@@ -1,13 +1,40 @@
1
+ import type { HTMLAttributes } from "svelte/elements";
2
+ import { type SkeletonStrings } from "../util/skeleton.js";
1
3
  type Variant = "text" | "circle" | "block";
2
- interface Props {
4
+ type Tone = "neutral" | "on-color";
5
+ /**
6
+ * Anything else a `div` takes (`id`, `data-testid`, `style`, `aria-*`) goes to the piece, or to the
7
+ * paragraph's wrapper with `lines`. `role`, `aria-busy`, `aria-hidden` and `data-skeleton` are set by
8
+ * the component and are not overridden.
9
+ */
10
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy" | "aria-hidden"> & {
3
11
  variant?: Variant;
4
12
  /** CSS length or px number; omit to use variant default (circle stays square). */
5
13
  width?: string | number;
6
- /** CSS length or px number; omit for variant default height. */
14
+ /** CSS length or px number; omit for variant default height. With `lines`, the height of each line. */
7
15
  height?: string | number;
8
16
  class?: string;
17
+ /** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
9
18
  "aria-label"?: string;
10
- }
19
+ /** Overrides for the built-in strings. */
20
+ strings?: Partial<SkeletonStrings>;
21
+ /**
22
+ * Draws the piece only: no role, no label, hidden from assistive tech.
23
+ * For a piece beside a status of your own. A piece under a `SkeletonGroup` is decorative already.
24
+ */
25
+ decorative?: boolean;
26
+ /**
27
+ * "neutral" is the quiet fill for a page or a card. "on-color" is the label colour of
28
+ * the surface it sits on at a low strength, for a brand, accent or fill block (`on-brand`,
29
+ * `on-accent`, `on-fill`, a `Card` with `tone`), in light and dark.
30
+ */
31
+ tone?: Tone;
32
+ /**
33
+ * Text variant only: draws this many lines (1 to 20) as one placeholder, the last one shorter.
34
+ * `class` and `width` then apply to the whole paragraph, `height` to each line.
35
+ */
36
+ lines?: number;
37
+ };
11
38
  declare const Skeleton: import("svelte").Component<Props, {}, "">;
12
39
  type Skeleton = ReturnType<typeof Skeleton>;
13
40
  export default Skeleton;
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ import { cn } from "../util/cn.js";
6
+ import { mergeStrings } from "../util/ready-made-strings.js";
7
+ import { DEFAULT_SKELETON_STRINGS, provideSkeletonGroup, type SkeletonStrings } from "../util/skeleton.js";
8
+ import { zabiStringsFor } from "../util/zabi-strings.js";
9
+
10
+ /**
11
+ * One loading status for a set of `Skeleton` pieces. The group is the
12
+ * `role="status"` region with the label; every `Skeleton` inside it is
13
+ * hidden from assistive tech, so a screen's eight pieces are announced
14
+ * once, not eight times.
15
+ */
16
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-busy"> & {
17
+ /** The status label. Defaults to the provider's `skeleton.label`, then "Loading…". */
18
+ "aria-label"?: string;
19
+ /** Overrides for the built-in strings. */
20
+ strings?: Partial<SkeletonStrings>;
21
+ class?: string;
22
+ children?: Snippet;
23
+ };
24
+
25
+ let { "aria-label": ariaLabel, strings, class: className = "", children, ...restProps }: Props = $props();
26
+
27
+ provideSkeletonGroup();
28
+
29
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
30
+ const provided = zabiStringsFor("skeleton");
31
+ const text = $derived(mergeStrings(DEFAULT_SKELETON_STRINGS, provided(), strings));
32
+ </script>
33
+
34
+ <div
35
+ {...restProps}
36
+ class={cn(className)}
37
+ role="status"
38
+ aria-busy="true"
39
+ aria-label={ariaLabel ?? text.label}
40
+ data-skeleton-group
41
+ >
42
+ {@render children?.()}
43
+ </div>