zabi-components 7.0.2 → 8.1.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 (433) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +287 -11
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1329 -163
  5. package/dist/atoms/ActionPanel.svelte +24 -9
  6. package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
  7. package/dist/atoms/Avatar.svelte +104 -0
  8. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  9. package/dist/atoms/Badge.svelte +124 -58
  10. package/dist/atoms/Badge.svelte.d.ts +17 -5
  11. package/dist/atoms/Button.svelte +212 -62
  12. package/dist/atoms/Button.svelte.d.ts +33 -2
  13. package/dist/atoms/Card.svelte +31 -28
  14. package/dist/atoms/Card.svelte.d.ts +5 -2
  15. package/dist/atoms/CardContent.svelte +10 -2
  16. package/dist/atoms/CardContent.svelte.d.ts +2 -0
  17. package/dist/atoms/CardFooter.svelte +10 -2
  18. package/dist/atoms/CardFooter.svelte.d.ts +2 -0
  19. package/dist/atoms/CardHeader.svelte +26 -6
  20. package/dist/atoms/CardHeader.svelte.d.ts +2 -0
  21. package/dist/atoms/Checkbox.svelte +52 -21
  22. package/dist/atoms/Checkbox.svelte.d.ts +6 -2
  23. package/dist/atoms/CodeBlock.svelte +35 -9
  24. package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
  25. package/dist/atoms/ColorPicker.svelte +272 -52
  26. package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
  27. package/dist/atoms/Container.svelte +17 -10
  28. package/dist/atoms/DateField.svelte +107 -0
  29. package/dist/atoms/DateField.svelte.d.ts +53 -0
  30. package/dist/atoms/Divider.svelte +4 -4
  31. package/dist/atoms/FeatureCard.svelte +4 -3
  32. package/dist/atoms/FieldMessages.svelte +69 -0
  33. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  34. package/dist/atoms/FloatingActionButton.svelte +174 -0
  35. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  36. package/dist/atoms/Heading.svelte +93 -32
  37. package/dist/atoms/Heading.svelte.d.ts +24 -4
  38. package/dist/atoms/IconButton.svelte +242 -53
  39. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  40. package/dist/atoms/Input.svelte +267 -74
  41. package/dist/atoms/Input.svelte.d.ts +32 -2
  42. package/dist/atoms/List.svelte +13 -2
  43. package/dist/atoms/List.svelte.d.ts +2 -0
  44. package/dist/atoms/ListItem.svelte +44 -11
  45. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  46. package/dist/atoms/ListItemLeading.svelte +4 -2
  47. package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
  48. package/dist/atoms/OptimizedImage.svelte +11 -2
  49. package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
  50. package/dist/atoms/Progress.svelte +31 -9
  51. package/dist/atoms/Progress.svelte.d.ts +6 -0
  52. package/dist/atoms/Radio.svelte +23 -3
  53. package/dist/atoms/Radio.svelte.d.ts +6 -2
  54. package/dist/atoms/Rating.svelte +494 -0
  55. package/dist/atoms/Rating.svelte.d.ts +56 -0
  56. package/dist/atoms/Select.svelte +475 -135
  57. package/dist/atoms/Select.svelte.d.ts +52 -2
  58. package/dist/atoms/SelectionControl.svelte +99 -18
  59. package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
  60. package/dist/atoms/Skeleton.svelte +5 -3
  61. package/dist/atoms/Slider.svelte +309 -0
  62. package/dist/atoms/Slider.svelte.d.ts +40 -0
  63. package/dist/atoms/Spinner.svelte +57 -0
  64. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  65. package/dist/atoms/Table.svelte +152 -6
  66. package/dist/atoms/Table.svelte.d.ts +15 -3
  67. package/dist/atoms/TemporalField.svelte +140 -0
  68. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  69. package/dist/atoms/Text.svelte +56 -15
  70. package/dist/atoms/Text.svelte.d.ts +13 -4
  71. package/dist/atoms/Textarea.svelte +57 -45
  72. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  73. package/dist/atoms/ThemeToggle.svelte +238 -56
  74. package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
  75. package/dist/atoms/TimeField.svelte +108 -0
  76. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  77. package/dist/atoms/Toast.svelte +25 -10
  78. package/dist/atoms/Toast.svelte.d.ts +2 -0
  79. package/dist/atoms/Toggle.svelte +60 -19
  80. package/dist/atoms/Toggle.svelte.d.ts +18 -2
  81. package/dist/atoms/Tooltip.svelte +583 -40
  82. package/dist/atoms/Tooltip.svelte.d.ts +26 -0
  83. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  84. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  85. package/dist/atoms/index.d.ts +17 -0
  86. package/dist/atoms/index.js +12 -0
  87. package/dist/atoms/selection-control.styles.d.ts +36 -5
  88. package/dist/atoms/selection-control.styles.js +40 -9
  89. package/dist/components/atoms/index.d.ts +17 -0
  90. package/dist/components/atoms/index.d.ts.map +1 -1
  91. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  92. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  93. package/dist/components/index.d.ts +83 -3
  94. package/dist/components/index.d.ts.map +1 -1
  95. package/dist/components/molecules/index.d.ts +46 -0
  96. package/dist/components/molecules/index.d.ts.map +1 -1
  97. package/dist/components/molecules/toast-store.d.ts +53 -7
  98. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  99. package/dist/components/organisms/index.d.ts +6 -0
  100. package/dist/components/organisms/index.d.ts.map +1 -1
  101. package/dist/components/types/variants.d.ts +15 -1
  102. package/dist/components/types/variants.d.ts.map +1 -1
  103. package/dist/components/util/app-shell.d.ts +28 -0
  104. package/dist/components/util/app-shell.d.ts.map +1 -0
  105. package/dist/components/util/avatar.d.ts +36 -0
  106. package/dist/components/util/avatar.d.ts.map +1 -0
  107. package/dist/components/util/bottom-sheet.d.ts +30 -0
  108. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  109. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  110. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  111. package/dist/components/util/calendar.d.ts +56 -0
  112. package/dist/components/util/calendar.d.ts.map +1 -0
  113. package/dist/components/util/cn.d.ts +8 -0
  114. package/dist/components/util/cn.d.ts.map +1 -0
  115. package/dist/components/util/collapsible.d.ts +58 -0
  116. package/dist/components/util/collapsible.d.ts.map +1 -0
  117. package/dist/components/util/confirm-dialog.d.ts +12 -0
  118. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  119. package/dist/components/util/date.d.ts +96 -0
  120. package/dist/components/util/date.d.ts.map +1 -0
  121. package/dist/components/util/drawer.d.ts +13 -0
  122. package/dist/components/util/drawer.d.ts.map +1 -0
  123. package/dist/components/util/dropdown.d.ts +47 -0
  124. package/dist/components/util/dropdown.d.ts.map +1 -0
  125. package/dist/components/util/field.d.ts +39 -0
  126. package/dist/components/util/field.d.ts.map +1 -0
  127. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  128. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  129. package/dist/components/util/focus-utils.d.ts +75 -2
  130. package/dist/components/util/focus-utils.d.ts.map +1 -1
  131. package/dist/components/util/hydration.d.ts +45 -0
  132. package/dist/components/util/hydration.d.ts.map +1 -0
  133. package/dist/components/util/keyboard-inset.d.ts +18 -0
  134. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  135. package/dist/components/util/media-grid.d.ts +51 -0
  136. package/dist/components/util/media-grid.d.ts.map +1 -0
  137. package/dist/components/util/media.d.ts +6 -0
  138. package/dist/components/util/media.d.ts.map +1 -0
  139. package/dist/components/util/overlay-chrome.d.ts +53 -0
  140. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  141. package/dist/components/util/overlay.d.ts +72 -0
  142. package/dist/components/util/overlay.d.ts.map +1 -0
  143. package/dist/components/util/photo.d.ts +215 -0
  144. package/dist/components/util/photo.d.ts.map +1 -0
  145. package/dist/components/util/portal.d.ts +23 -0
  146. package/dist/components/util/portal.d.ts.map +1 -0
  147. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  148. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  149. package/dist/components/util/radio-keys.d.ts +25 -0
  150. package/dist/components/util/radio-keys.d.ts.map +1 -0
  151. package/dist/components/util/rating.d.ts +23 -0
  152. package/dist/components/util/rating.d.ts.map +1 -0
  153. package/dist/components/util/ready-made-strings.d.ts +77 -0
  154. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  155. package/dist/components/util/scroll-padding.d.ts +20 -0
  156. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  157. package/dist/components/util/segmented-control.d.ts +16 -0
  158. package/dist/components/util/segmented-control.d.ts.map +1 -0
  159. package/dist/components/util/select.d.ts +41 -0
  160. package/dist/components/util/select.d.ts.map +1 -0
  161. package/dist/components/util/sheet-drag.d.ts +60 -0
  162. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  163. package/dist/components/util/sidebar.d.ts +63 -0
  164. package/dist/components/util/sidebar.d.ts.map +1 -0
  165. package/dist/components/util/sortable-list.d.ts +65 -0
  166. package/dist/components/util/sortable-list.d.ts.map +1 -0
  167. package/dist/components/util/ssr-safe.d.ts +4 -4
  168. package/dist/components/util/ssr-safe.d.ts.map +1 -1
  169. package/dist/components/util/stepper.d.ts +40 -0
  170. package/dist/components/util/stepper.d.ts.map +1 -0
  171. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  172. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  173. package/dist/components/util/theme-mode.d.ts +80 -0
  174. package/dist/components/util/theme-mode.d.ts.map +1 -0
  175. package/dist/components/util/toaster.d.ts +93 -0
  176. package/dist/components/util/toaster.d.ts.map +1 -0
  177. package/dist/components/util/tooltip.d.ts +10 -0
  178. package/dist/components/util/tooltip.d.ts.map +1 -0
  179. package/dist/components/util/top-navbar.d.ts +11 -0
  180. package/dist/components/util/top-navbar.d.ts.map +1 -0
  181. package/dist/components/util/touch-target.d.ts +31 -0
  182. package/dist/components/util/touch-target.d.ts.map +1 -0
  183. package/dist/components/util/zabi-strings.d.ts +133 -0
  184. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  185. package/dist/create-theme/cli.js +169 -0
  186. package/dist/create-theme/index.d.ts +176 -0
  187. package/dist/create-theme/index.js +817 -0
  188. package/dist/create-theme/lib/ramp-math.js +128 -0
  189. package/dist/create-theme/lib/resolve.js +141 -0
  190. package/dist/create-theme/theme-data.js +1116 -0
  191. package/dist/index.d.ts +83 -3
  192. package/dist/index.js +60 -8
  193. package/dist/molecules/Alert.svelte +73 -66
  194. package/dist/molecules/Alert.svelte.d.ts +9 -1
  195. package/dist/molecules/AppBar.svelte +415 -0
  196. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  197. package/dist/molecules/AvatarGroup.svelte +116 -0
  198. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  199. package/dist/molecules/BottomSheet.svelte +540 -0
  200. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  201. package/dist/molecules/BottomTabBar.svelte +297 -0
  202. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  203. package/dist/molecules/Calendar.svelte +597 -0
  204. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  205. package/dist/molecules/Collapsible.svelte +243 -0
  206. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  207. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  208. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  209. package/dist/molecules/ComponentDemo.svelte +25 -5
  210. package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
  211. package/dist/molecules/ConfirmDialog.svelte +253 -0
  212. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  213. package/dist/molecules/ContactForm.svelte +46 -19
  214. package/dist/molecules/ContactForm.svelte.d.ts +10 -0
  215. package/dist/molecules/Drawer.svelte +394 -0
  216. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  217. package/dist/molecules/Dropdown.svelte +540 -50
  218. package/dist/molecules/Dropdown.svelte.d.ts +49 -6
  219. package/dist/molecules/DropdownItem.svelte +122 -0
  220. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  221. package/dist/molecules/EmptyState.svelte +56 -10
  222. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  223. package/dist/molecules/Form.svelte +25 -14
  224. package/dist/molecules/Form.svelte.d.ts +13 -5
  225. package/dist/molecules/FormField.svelte +11 -2
  226. package/dist/molecules/FormField.svelte.d.ts +2 -0
  227. package/dist/molecules/Header.svelte +13 -3
  228. package/dist/molecules/Header.svelte.d.ts +2 -0
  229. package/dist/molecules/ImageUpload.svelte +422 -70
  230. package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
  231. package/dist/molecules/MediaGrid.svelte +527 -0
  232. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  233. package/dist/molecules/Modal.svelte +340 -45
  234. package/dist/molecules/Modal.svelte.d.ts +59 -7
  235. package/dist/molecules/NavigationMenu.svelte +15 -3
  236. package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
  237. package/dist/molecules/NavigationMenuContent.svelte +91 -36
  238. package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
  239. package/dist/molecules/NavigationMenuItem.svelte +10 -3
  240. package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
  241. package/dist/molecules/NavigationMenuLink.svelte +10 -2
  242. package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
  243. package/dist/molecules/NavigationMenuList.svelte +19 -4
  244. package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
  245. package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
  246. package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
  247. package/dist/molecules/Page.svelte +38 -2
  248. package/dist/molecules/Page.svelte.d.ts +13 -0
  249. package/dist/molecules/PhotoGrid.svelte +433 -0
  250. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  251. package/dist/molecules/PhotoViewer.svelte +1116 -0
  252. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  253. package/dist/molecules/PropsTable.svelte +26 -11
  254. package/dist/molecules/PropsTable.svelte.d.ts +5 -0
  255. package/dist/molecules/PullToRefresh.svelte +263 -0
  256. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  257. package/dist/molecules/RadioGroup.svelte +57 -15
  258. package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
  259. package/dist/molecules/Section.svelte +13 -5
  260. package/dist/molecules/Section.svelte.d.ts +3 -1
  261. package/dist/molecules/SegmentedControl.svelte +323 -0
  262. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  263. package/dist/molecules/SidebarBrandHeader.svelte +24 -5
  264. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
  265. package/dist/molecules/SidebarFooter.svelte +39 -8
  266. package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
  267. package/dist/molecules/SidebarNavSection.svelte +12 -4
  268. package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
  269. package/dist/molecules/SlideUp.svelte +253 -30
  270. package/dist/molecules/SlideUp.svelte.d.ts +31 -4
  271. package/dist/molecules/SortableList.svelte +632 -0
  272. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  273. package/dist/molecules/Stepper.svelte +548 -0
  274. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  275. package/dist/molecules/StickyActionBar.svelte +191 -0
  276. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  277. package/dist/molecules/SwipeableListItem.svelte +334 -0
  278. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  279. package/dist/molecules/Tabs.svelte +254 -24
  280. package/dist/molecules/Tabs.svelte.d.ts +15 -4
  281. package/dist/molecules/Toaster.svelte +352 -6
  282. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  283. package/dist/molecules/ToasterToast.svelte +279 -62
  284. package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
  285. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  286. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  287. package/dist/molecules/index.d.ts +46 -0
  288. package/dist/molecules/index.js +27 -0
  289. package/dist/molecules/toast-store.d.ts +53 -7
  290. package/dist/molecules/toast-store.js +21 -0
  291. package/dist/organisms/AppShell.svelte +196 -0
  292. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  293. package/dist/organisms/SidebarAccountPanel.svelte +111 -12
  294. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
  295. package/dist/organisms/SidebarNavigation.svelte +193 -69
  296. package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
  297. package/dist/organisms/SidebarPanel.svelte +45 -16
  298. package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
  299. package/dist/organisms/SidebarShell.svelte +263 -0
  300. package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
  301. package/dist/organisms/TopNavbar.svelte +329 -33
  302. package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
  303. package/dist/organisms/index.d.ts +6 -0
  304. package/dist/organisms/index.js +4 -0
  305. package/dist/routes/lib/variant-utils.d.ts +26 -0
  306. package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
  307. package/dist/types/components.d.ts +4 -0
  308. package/dist/types/components.d.ts.map +1 -1
  309. package/dist/types/events.js +1 -0
  310. package/dist/types/index.d.ts +605 -25
  311. package/dist/types/index.d.ts.map +1 -1
  312. package/dist/types/index.js +3 -0
  313. package/dist/types/variants.ts +17 -1
  314. package/dist/util/app-shell.d.ts +27 -0
  315. package/dist/util/app-shell.js +72 -0
  316. package/dist/util/avatar.d.ts +35 -0
  317. package/dist/util/avatar.js +52 -0
  318. package/dist/util/bottom-sheet.d.ts +29 -0
  319. package/dist/util/bottom-sheet.js +43 -0
  320. package/dist/util/bottom-tab-bar.d.ts +34 -0
  321. package/dist/util/bottom-tab-bar.js +54 -0
  322. package/dist/util/calendar.d.ts +55 -0
  323. package/dist/util/calendar.js +74 -0
  324. package/dist/util/cn.d.ts +7 -0
  325. package/dist/util/cn.js +48 -0
  326. package/dist/util/collapsible.d.ts +57 -0
  327. package/dist/util/collapsible.js +23 -0
  328. package/dist/util/confirm-dialog.d.ts +11 -0
  329. package/dist/util/confirm-dialog.js +2 -0
  330. package/dist/util/date.d.ts +95 -0
  331. package/dist/util/date.js +237 -0
  332. package/dist/util/drawer.d.ts +12 -0
  333. package/dist/util/drawer.js +7 -0
  334. package/dist/util/dropdown.d.ts +46 -0
  335. package/dist/util/dropdown.js +21 -0
  336. package/dist/util/field.d.ts +38 -0
  337. package/dist/util/field.js +38 -0
  338. package/dist/util/fit-in-viewport.d.ts +200 -0
  339. package/dist/util/fit-in-viewport.js +399 -0
  340. package/dist/util/focus-utils.d.ts +75 -2
  341. package/dist/util/focus-utils.js +182 -9
  342. package/dist/util/hydration.d.ts +44 -0
  343. package/dist/util/hydration.js +58 -0
  344. package/dist/util/keyboard-inset.d.ts +17 -0
  345. package/dist/util/keyboard-inset.js +53 -0
  346. package/dist/util/media-grid.d.ts +50 -0
  347. package/dist/util/media-grid.js +81 -0
  348. package/dist/util/media.d.ts +5 -0
  349. package/dist/util/media.js +10 -0
  350. package/dist/util/overlay-chrome.d.ts +52 -0
  351. package/dist/util/overlay-chrome.js +56 -0
  352. package/dist/util/overlay.d.ts +71 -0
  353. package/dist/util/overlay.js +240 -0
  354. package/dist/util/photo.d.ts +214 -0
  355. package/dist/util/photo.js +206 -0
  356. package/dist/util/portal.d.ts +22 -0
  357. package/dist/util/portal.js +50 -0
  358. package/dist/util/pull-to-refresh.d.ts +26 -0
  359. package/dist/util/pull-to-refresh.js +30 -0
  360. package/dist/util/radio-keys.d.ts +24 -0
  361. package/dist/util/radio-keys.js +49 -0
  362. package/dist/util/rating.d.ts +22 -0
  363. package/dist/util/rating.js +26 -0
  364. package/dist/util/ready-made-strings.d.ts +76 -0
  365. package/dist/util/ready-made-strings.js +67 -0
  366. package/dist/util/scroll-padding.d.ts +19 -0
  367. package/dist/util/scroll-padding.js +44 -0
  368. package/dist/util/segmented-control.d.ts +15 -0
  369. package/dist/util/segmented-control.js +1 -0
  370. package/dist/util/select.d.ts +40 -0
  371. package/dist/util/select.js +63 -0
  372. package/dist/util/sheet-drag.d.ts +59 -0
  373. package/dist/util/sheet-drag.js +241 -0
  374. package/dist/util/sidebar.d.ts +62 -0
  375. package/dist/util/sidebar.js +38 -0
  376. package/dist/util/sortable-list.d.ts +64 -0
  377. package/dist/util/sortable-list.js +53 -0
  378. package/dist/util/ssr-safe.d.ts +4 -4
  379. package/dist/util/stepper.d.ts +39 -0
  380. package/dist/util/stepper.js +38 -0
  381. package/dist/util/swipeable-list-item.d.ts +35 -0
  382. package/dist/util/swipeable-list-item.js +32 -0
  383. package/dist/util/theme-mode.d.ts +79 -0
  384. package/dist/util/theme-mode.js +121 -0
  385. package/dist/util/toaster.d.ts +92 -0
  386. package/dist/util/toaster.js +75 -0
  387. package/dist/util/tooltip.d.ts +9 -0
  388. package/dist/util/tooltip.js +18 -0
  389. package/dist/util/top-navbar.d.ts +10 -0
  390. package/dist/util/top-navbar.js +5 -0
  391. package/dist/util/touch-target.d.ts +30 -0
  392. package/dist/util/touch-target.js +30 -0
  393. package/dist/util/zabi-strings.d.ts +132 -0
  394. package/dist/util/zabi-strings.js +105 -0
  395. package/dist/zabi-components-colors.css +5 -2
  396. package/dist/zabi-components-theme-dark-only.css +574 -225
  397. package/dist/zabi-components-theme-dark.css +574 -225
  398. package/dist/zabi-components-theme-only.css +1680 -317
  399. package/dist/zabi-components-theme.css +1680 -317
  400. package/dist/zabi-components.css +4473 -1158
  401. package/docs/lucide-icons.md +41 -16
  402. package/docs/theme-imports.md +81 -11
  403. package/package.json +51 -29
  404. package/dist/lib/layout-width-tokens.d.ts +0 -17
  405. package/dist/lib/layout-width-tokens.d.ts.map +0 -1
  406. package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
  407. package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
  408. package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
  409. package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
  410. package/dist/lib/showcase/component-docs/List.d.ts +0 -3
  411. package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
  412. package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
  413. package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
  414. package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
  415. package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
  416. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
  417. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
  418. package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
  419. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
  420. package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
  421. package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
  422. package/dist/lib/showcase/component-docs/index.d.ts +0 -4
  423. package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
  424. package/dist/lib/showcase/components-catalog.d.ts +0 -3
  425. package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
  426. package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
  427. package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
  428. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
  429. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
  430. package/dist/routes/lib/focus-utils.d.ts +0 -12
  431. package/dist/routes/lib/focus-utils.ts +0 -110
  432. package/dist/routes/lib/ssr-safe.ts +0 -58
  433. package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
@@ -1,9 +1,28 @@
1
1
  <script lang="ts">
2
- import { CheckCircle, AlertTriangle, AlertCircle } from '@lucide/svelte';
2
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
3
3
  import type { SemanticVariant, SizeVariant } from '../types/variants.js';
4
4
  import { generateId } from "../util/ssr-safe.js";
5
+ import { cn } from "../util/cn.js";
6
+ import { fieldDescribedBy, fieldMessageState } from "../util/field.js";
7
+ import FieldMessages from "./FieldMessages.svelte";
5
8
 
6
- interface Props {
9
+ /**
10
+ * Other attributes (`autocomplete`, `inputmode`, `maxlength`,
11
+ * `enterkeyhint`, `data-*`, `onchange`, ...) land on the `<textarea>`.
12
+ */
13
+ type Props = Omit<
14
+ HTMLTextareaAttributes,
15
+ | 'class'
16
+ | 'value'
17
+ | 'id'
18
+ | 'name'
19
+ | 'placeholder'
20
+ | 'required'
21
+ | 'disabled'
22
+ | 'rows'
23
+ | 'oninput'
24
+ | 'aria-describedby'
25
+ > & {
7
26
  id?: string;
8
27
  value?: string;
9
28
  name?: string;
@@ -17,13 +36,20 @@
17
36
  rows?: number;
18
37
  size?: SizeVariant;
19
38
  variant?: SemanticVariant;
39
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
20
40
  message?: string;
41
+ /** Help text under the field, read out with it. */
42
+ hint?: string;
43
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
44
+ error?: string;
21
45
  oninput?: (event: Event) => void;
22
- }
46
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
47
+ "aria-describedby"?: string | null;
48
+ };
23
49
 
24
50
  let {
25
51
  id: idProp,
26
- value = $bindable(''),
52
+ value = $bindable<Exclude<Props["value"], undefined>>(),
27
53
  name = '',
28
54
  class: className = '',
29
55
  label = '',
@@ -36,53 +62,52 @@
36
62
  size = 'md',
37
63
  variant = 'default',
38
64
  message = '',
65
+ hint = '',
66
+ error = '',
39
67
  oninput,
68
+ 'aria-describedby': describedBy,
40
69
  ...restProps
41
70
  }: Props = $props();
42
71
 
72
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
73
+ // one that has a fallback (`props_invalid_value`), and a page that throws
74
+ // while it hydrates never becomes interactive. The default is applied
75
+ // here instead: at once, for the server and the first render, and again
76
+ // whenever a parent hands back `undefined`.
77
+ const applyDefaults = () => {
78
+ if (value === undefined) value = '';
79
+ };
80
+ applyDefaults();
81
+ $effect.pre(applyDefaults);
82
+
43
83
  const fallbackId = generateId('textarea');
44
84
  const textareaId = $derived(idProp ?? fallbackId);
45
85
  const isDisabled = $derived(disabled || loading);
46
86
 
87
+ const status = $derived(fieldMessageState({ variant, message, hint, error }));
88
+
47
89
  const variantClass = $derived(() => {
48
- return variant === 'success'
90
+ return status.variant === 'success'
49
91
  ? 'border-success focus-visible:border-success'
50
- : variant === 'warning'
92
+ : status.variant === 'warning'
51
93
  ? 'border-warning focus-visible:border-warning'
52
- : variant === 'error'
94
+ : status.variant === 'error'
53
95
  ? 'border-error focus-visible:border-error'
54
- : 'border-input-border';
96
+ : 'border-input-border enabled:hover:border-input-border-hover';
55
97
  });
56
98
 
99
+ // 16px below `sm`: iOS Safari zooms the page when a focused field is smaller.
57
100
  const textareaClasses = $derived(() => {
58
101
  const baseClasses =
59
- 'focus-ring w-full border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus-visible:outline-none disabled:opacity-50 disabled:cursor-not-allowed resize-y px-4 py-2.5 text-base leading-6';
102
+ '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 resize-y px-3 py-2 text-sm max-sm:text-base leading-6';
60
103
 
61
- return `${baseClasses} ${variantClass()} ${className}`.trim();
104
+ return cn(`${baseClasses} ${variantClass()} ${className}`);
62
105
  });
63
106
 
64
107
  const labelClasses = $derived(
65
108
  () => 'mb-1 block text-sm font-medium text-label',
66
109
  );
67
110
 
68
- const messageClasses = $derived(() => {
69
- if (variant === 'error') {
70
- return 'mt-1 flex items-center gap-1.5 text-sm text-error';
71
- } else if (variant === 'success') {
72
- return 'mt-1 flex items-center gap-1.5 text-sm text-success';
73
- } else if (variant === 'warning') {
74
- return 'mt-1 flex items-center gap-1.5 text-sm text-warning';
75
- }
76
- return 'mt-1 flex items-center gap-1.5 text-sm text-description';
77
- });
78
-
79
- const getIcon = $derived(() => {
80
- if (variant === 'error') return AlertCircle;
81
- if (variant === 'success') return CheckCircle;
82
- if (variant === 'warning') return AlertTriangle;
83
- return null;
84
- });
85
-
86
111
  function handleInput(event: Event) {
87
112
  const target = event.target as HTMLTextAreaElement;
88
113
  value = target.value;
@@ -105,10 +130,10 @@
105
130
  {rows}
106
131
  class={textareaClasses()}
107
132
  oninput={handleInput}
108
- aria-invalid={variant === 'error' ? 'true' : undefined}
133
+ aria-invalid={status.variant === 'error' ? 'true' : undefined}
109
134
  aria-required={required ? 'true' : undefined}
110
135
  aria-busy={loading ? 'true' : undefined}
111
- aria-describedby={message ? `${textareaId}-message` : undefined}
136
+ aria-describedby={fieldDescribedBy(textareaId, status, describedBy)}
112
137
  {...restProps}
113
138
  ></textarea>
114
139
  {#if loading}
@@ -117,23 +142,10 @@
117
142
  aria-hidden="true"
118
143
  >
119
144
  <span
120
- class="inline-block size-5 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70"
145
+ class="inline-block size-5 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70 motion-reduce:animate-pulse"
121
146
  ></span>
122
147
  </span>
123
148
  {/if}
124
149
  </div>
125
- {#if message && variant !== 'default'}
126
- <p
127
- id={`${textareaId}-message`}
128
- class={messageClasses()}
129
- role={variant === 'error' ? 'alert' : 'status'}
130
- aria-live={variant === 'error' ? 'assertive' : 'polite'}
131
- >
132
- {#if getIcon()}
133
- {@const Icon = getIcon()}
134
- <Icon size={14} class="shrink-0" />
135
- {/if}
136
- <span>{message}</span>
137
- </p>
138
- {/if}
150
+ <FieldMessages fieldId={textareaId} {status} {hint} gap="mt-1" />
139
151
  </div>
@@ -1,5 +1,10 @@
1
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
1
2
  import type { SemanticVariant, SizeVariant } from '../types/variants.js';
2
- interface Props {
3
+ /**
4
+ * Other attributes (`autocomplete`, `inputmode`, `maxlength`,
5
+ * `enterkeyhint`, `data-*`, `onchange`, ...) land on the `<textarea>`.
6
+ */
7
+ type Props = Omit<HTMLTextareaAttributes, 'class' | 'value' | 'id' | 'name' | 'placeholder' | 'required' | 'disabled' | 'rows' | 'oninput' | 'aria-describedby'> & {
3
8
  id?: string;
4
9
  value?: string;
5
10
  name?: string;
@@ -13,9 +18,16 @@ interface Props {
13
18
  rows?: number;
14
19
  size?: SizeVariant;
15
20
  variant?: SemanticVariant;
21
+ /** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
16
22
  message?: string;
23
+ /** Help text under the field, read out with it. */
24
+ hint?: string;
25
+ /** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
26
+ error?: string;
17
27
  oninput?: (event: Event) => void;
18
- }
28
+ /** Ids of other elements that describe the field. The hint and the message are added after them. */
29
+ "aria-describedby"?: string | null;
30
+ };
19
31
  declare const Textarea: import("svelte").Component<Props, {}, "value">;
20
32
  type Textarea = ReturnType<typeof Textarea>;
21
33
  export default Textarea;
@@ -1,85 +1,251 @@
1
1
  <script lang="ts">
2
- import { onMount } from "svelte";
3
- import { Sun, Moon } from "@lucide/svelte";
2
+ import { mergeStrings } from "../util/ready-made-strings.js";
3
+ import { zabiStringsFor } from "../util/zabi-strings.js";
4
+ import { onMount, untrack } from "svelte";
5
+ import type { HTMLButtonAttributes } from "svelte/elements";
6
+ import Sun from "@lucide/svelte/icons/sun";
7
+ import Moon from "@lucide/svelte/icons/moon";
8
+ import Monitor from "@lucide/svelte/icons/monitor";
4
9
  import { FOCUS_BRAND_CLASS } from "../util/focus-utils.js";
5
-
6
- function safeLocalStorage(): Storage | undefined {
7
- return typeof window !== "undefined" ? localStorage : undefined;
8
- }
9
-
10
- function safeDocument(): Document | undefined {
11
- return typeof window !== "undefined" ? document : undefined;
12
- }
10
+ import { cn } from "../util/cn.js";
11
+ import {
12
+ DEFAULT_THEME_STORAGE_KEY,
13
+ THEME_MODES,
14
+ getStoredThemeMode,
15
+ getThemeMode,
16
+ isThemeDark,
17
+ isThemeMode,
18
+ setThemeMode,
19
+ storeThemeMode,
20
+ type ThemeMode,
21
+ type ThemeToggleLabels,
22
+ } from "../util/theme-mode.js";
13
23
 
14
- interface Props {
24
+ /** Other attributes (`data-*`, `aria-*`, `id`, ...) land on the `<button>`. */
25
+ type Props = Omit<
26
+ HTMLButtonAttributes,
27
+ "class" | "disabled" | "type" | "onclick" | "children"
28
+ > & {
29
+ /** Extra classes for the host element. */
30
+ class?: string;
15
31
  size?: "sm" | "md" | "lg";
16
32
  variant?: "default" | "ghost" | "outline";
17
33
  disabled?: boolean;
34
+ /**
35
+ * `"two"` (the default) flips light and dark. `"three"` steps through
36
+ * system, light and dark, and always writes `data-theme` on `<html>`.
37
+ */
38
+ modes?: "two" | "three";
39
+ /**
40
+ * The page's mode: `"auto"` (follow the system), `"light"` or `"dark"`.
41
+ * Bindable. It is read from `<html>` when the button mounts and follows
42
+ * the page from then on; assigning it switches the page.
43
+ */
44
+ mode?: ThemeMode;
45
+ /** Called with the new mode when a press changes it. */
46
+ onmodechange?: (mode: ThemeMode) => void;
47
+ /**
48
+ * The `localStorage` key the choice is kept under and restored from when
49
+ * the button mounts. `null` keeps nothing.
50
+ */
51
+ storageKey?: string | null;
52
+ /** Replaces the texts of the accessible name, for another language. */
53
+ labels?: Partial<ThemeToggleLabels>;
18
54
  onclick?: (event: Event) => void;
19
- }
55
+ };
20
56
 
21
57
  let {
58
+ class: className = "",
22
59
  size = "md",
23
60
  variant = "default",
24
61
  disabled = false,
62
+ modes = "two",
63
+ mode = $bindable(),
64
+ onmodechange,
65
+ storageKey = DEFAULT_THEME_STORAGE_KEY,
66
+ labels,
67
+ onclick,
25
68
  ...restProps
26
69
  }: Props = $props();
27
70
 
71
+ const DEFAULT_LABELS: ThemeToggleLabels = {
72
+ auto: "system",
73
+ light: "light",
74
+ dark: "dark",
75
+ describe: (current, next) => `Theme: ${current}. Switch to ${next}`,
76
+ darkMode: "Dark mode",
77
+ beforeMount: "Theme toggle",
78
+ };
79
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
80
+ const provided = zabiStringsFor("themeToggle");
81
+ const text = $derived(mergeStrings(DEFAULT_LABELS, provided(), labels));
82
+
83
+ /** Whether the page is dark right now, whichever way it is switched. */
28
84
  let isDark = $state(false);
85
+ /** The page's mode as last read from `<html>`. */
86
+ let pageMode = $state<ThemeMode>("light");
29
87
  let mounted = $state(false);
30
88
 
89
+ const SYSTEM_DARK = "(prefers-color-scheme: dark)";
90
+
91
+ /** A page that carries `data-theme` on `<html>` is switched through it; any other page through the class. */
92
+ function usesAttribute(root: HTMLElement): boolean {
93
+ return root.hasAttribute("data-theme");
94
+ }
95
+
96
+ /** Reads the page into the button. The only place `mode` is set from the page. */
97
+ function readPage() {
98
+ isDark = isThemeDark();
99
+ pageMode = getThemeMode();
100
+ mode = pageMode;
101
+ }
102
+
103
+ /**
104
+ * Puts the page in `next`. With three modes that is always `data-theme`.
105
+ * With two, "auto" can only be said with the attribute, and light or dark
106
+ * go to whichever mechanism the page uses, as they always have.
107
+ */
108
+ function applyMode(next: ThemeMode) {
109
+ if (modes === "three" || next === "auto") {
110
+ setThemeMode(next, { storageKey });
111
+ } else {
112
+ isDark = next === "dark";
113
+ updateTheme();
114
+ storeThemeMode(next, storageKey);
115
+ }
116
+ readPage();
117
+ }
118
+
31
119
  onMount(() => {
32
120
  mounted = true;
33
- const doc = safeDocument();
34
- if (doc) {
35
- isDark = doc.documentElement.classList.contains("dark");
121
+ if (typeof document === "undefined") return;
122
+ const root = document.documentElement;
123
+
124
+ clearInlineScheme(root);
125
+
126
+ // The choice made last time, unless a script in <head> has applied it
127
+ // already (`themeInitScript`) or the app passed a mode of its own.
128
+ const asked = isThemeMode(mode) ? mode : getStoredThemeMode(storageKey);
129
+ if (asked && asked !== getThemeMode() && (modes === "three" || asked !== "auto" || usesAttribute(root))) {
130
+ applyMode(asked);
131
+ } else {
132
+ readPage();
36
133
  }
37
134
 
38
- if (!window.matchMedia) return;
135
+ // The page can be switched by something else (another control bound to
136
+ // the same helpers, the app's own script): the icon and the name follow.
137
+ const observer = typeof MutationObserver === "undefined" ? undefined : new MutationObserver(readPage);
138
+ observer?.observe(root, { attributes: true, attributeFilter: ["class", "data-theme"] });
39
139
 
40
- const media = window.matchMedia("(prefers-color-scheme: dark)");
140
+ const media = window.matchMedia?.(SYSTEM_DARK);
41
141
  const handleSystemChange = (event: MediaQueryListEvent) => {
42
- const storage = safeLocalStorage();
43
- if (storage && storage.getItem("theme")) return;
142
+ if (modes === "three" || usesAttribute(root)) {
143
+ // `data-theme="auto"` follows the system in the stylesheet, and
144
+ // "light" or "dark" is a choice: either way only the reading changes.
145
+ readPage();
146
+ return;
147
+ }
148
+ if (getStoredThemeMode(storageKey)) return;
44
149
  isDark = event.matches;
45
150
  updateTheme();
151
+ readPage();
152
+ };
153
+ media?.addEventListener("change", handleSystemChange);
154
+
155
+ return () => {
156
+ observer?.disconnect();
157
+ media?.removeEventListener("change", handleSystemChange);
46
158
  };
159
+ });
47
160
 
48
- media.addEventListener("change", handleSystemChange);
49
- return () => media.removeEventListener("change", handleSystemChange);
161
+ // `mode` assigned from outside (a bound value, the app's own control).
162
+ $effect(() => {
163
+ const wanted = mode;
164
+ if (!mounted || !isThemeMode(wanted)) return;
165
+ untrack(() => {
166
+ if (wanted !== pageMode) applyMode(wanted);
167
+ });
50
168
  });
51
169
 
170
+ /** What a press leads to. */
171
+ const nextMode = $derived<ThemeMode>(
172
+ modes === "three"
173
+ ? THEME_MODES[(THEME_MODES.indexOf(pageMode) + 1) % THEME_MODES.length]
174
+ : isDark
175
+ ? "light"
176
+ : "dark",
177
+ );
178
+
179
+ /**
180
+ * Two modes: a switch with one name, "Dark mode", and `aria-pressed` for its
181
+ * state. The name used to flip as well ("Switch to dark mode", then "Switch
182
+ * to light mode, pressed"), and "pressed" on "Switch to light mode" says
183
+ * two things at once. Three modes are steps, not on and off, so there the
184
+ * name carries the state and there is no `aria-pressed`.
185
+ */
186
+ const accessibleName = $derived(modes === "three" ? text.describe(text[pageMode], text[nextMode]) : text.darkMode);
187
+
52
188
  function toggleTheme(event: Event) {
53
189
  if (disabled) return;
54
- isDark = !isDark;
55
- updateTheme();
56
- const storage = safeLocalStorage();
57
- if (mounted && storage) {
58
- storage.setItem("theme", isDark ? "dark" : "light");
59
- }
60
-
61
- if (onclick) {
62
- (onclick as (event: Event) => void)(event);
63
- }
190
+ applyMode(nextMode);
191
+ onmodechange?.(pageMode);
192
+ onclick?.(event);
64
193
  }
65
194
 
195
+ /** Two modes, light or dark: the attribute where the page has it, the class otherwise. */
66
196
  function updateTheme() {
67
- const doc = safeDocument();
68
- if (mounted && doc) {
69
- if (isDark) {
70
- doc.documentElement.classList.add("dark");
71
- doc.documentElement.style.colorScheme = "dark";
72
- } else {
73
- doc.documentElement.classList.remove("dark");
74
- doc.documentElement.style.colorScheme = "light";
75
- }
197
+ if (!mounted || typeof document === "undefined") return;
198
+ const root = document.documentElement;
199
+ if (usesAttribute(root)) {
200
+ // An explicit choice, also when the page was on "auto". The theme
201
+ // sets `color-scheme` for each value, so no inline style is written.
202
+ root.setAttribute("data-theme", isDark ? "dark" : "light");
203
+ // A leftover class would keep the page dark under data-theme="light".
204
+ if (!isDark) root.classList.remove("dark");
205
+ return;
76
206
  }
207
+ // The class only. The theme sets `color-scheme` for it (dark under
208
+ // `.dark`, light on the root otherwise), so no inline value is written:
209
+ // one left behind would outrank the stylesheet, and the next time the
210
+ // app added or removed the class itself the tokens would change and
211
+ // the native controls would not.
212
+ root.classList.toggle("dark", isDark);
213
+ clearInlineScheme(root);
214
+ }
215
+
216
+ /**
217
+ * Removes an inline `color-scheme` of "light" or "dark" from `<html>`: what
218
+ * this button wrote before the theme set the scheme itself, and what a
219
+ * start-up script copied from the old docs writes. Any other inline value
220
+ * is the app's own and is left alone.
221
+ */
222
+ function clearInlineScheme(root: HTMLElement) {
223
+ const inline = root.style.colorScheme;
224
+ if (inline === "light" || inline === "dark") root.style.removeProperty("color-scheme");
77
225
  }
78
226
 
227
+ /**
228
+ * Before mount every icon is rendered and CSS shows one, by the selectors
229
+ * the theme itself is switched with. Written out in full, because Tailwind
230
+ * only generates a class it can read in the source.
231
+ *
232
+ * Two modes: sun on a light page, moon on a dark one (the class,
233
+ * `data-theme="dark"`, or `data-theme="auto"` on a dark system).
234
+ * Three modes show the mode, not the result: the monitor for
235
+ * `data-theme="auto"` whatever the system is.
236
+ */
237
+ const SUN_BEFORE_MOUNT =
238
+ "[.dark_&]:hidden [[data-theme=dark]_&]:hidden [@media(prefers-color-scheme:dark)]:[[data-theme=auto]_&]:hidden";
239
+ const MOON_BEFORE_MOUNT =
240
+ "hidden [.dark_&]:block [[data-theme=dark]_&]:block [@media(prefers-color-scheme:dark)]:[[data-theme=auto]_&]:block";
241
+ const SUN_BEFORE_MOUNT_THREE = "[.dark_&]:hidden [[data-theme=dark]_&]:hidden [[data-theme=auto]_&]:hidden";
242
+ const MOON_BEFORE_MOUNT_THREE = "hidden [.dark_&]:block [[data-theme=dark]_&]:block";
243
+ const SYSTEM_BEFORE_MOUNT_THREE = "hidden [[data-theme=auto]_&]:block";
244
+
79
245
  const sizeClass = $derived(() => {
80
246
  if (size === "sm") {
81
247
  return {
82
- button: "w-8 h-8",
248
+ button: "w-8 h-8 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
83
249
  icon: 16
84
250
  };
85
251
  } else if (size === "lg") {
@@ -89,7 +255,7 @@
89
255
  };
90
256
  } else {
91
257
  return {
92
- button: "w-10 h-10",
258
+ button: "w-10 h-10 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
93
259
  icon: 20
94
260
  };
95
261
  }
@@ -97,20 +263,20 @@
97
263
 
98
264
  const variantClass = $derived(() => {
99
265
  if (variant === "ghost") {
100
- return "bg-transparent hover:bg-base-100 active:bg-base-200 border-0";
266
+ return "bg-transparent hover:bg-surface-hover active:bg-surface-active border-0";
101
267
  } else if (variant === "outline") {
102
- return "bg-base-50 hover:bg-base-100 active:bg-base-200 border border-border";
268
+ return "bg-action-secondary hover:bg-action-secondary-hover active:bg-action-secondary-active border border-border";
103
269
  } else {
104
- return "bg-base-50 hover:bg-base-100 active:bg-base-200 border-0";
270
+ return "bg-action-secondary hover:bg-action-secondary-hover active:bg-action-secondary-active border-0";
105
271
  }
106
272
  });
107
273
 
108
274
  const buttonClasses = $derived(() => {
109
275
  const sizeStyles = sizeClass();
110
- return `
276
+ return cn(`
111
277
  ${sizeStyles.button}
112
278
  ${variantClass()}
113
- rounded-lg
279
+ rounded-control
114
280
  flex
115
281
  items-center
116
282
  justify-center
@@ -122,23 +288,26 @@
122
288
  disabled:opacity-50
123
289
  disabled:cursor-not-allowed
124
290
  disabled:active:scale-100
125
- disabled:hover:bg-base-50
291
+ disabled:hover:bg-action-disabled
126
292
  ${FOCUS_BRAND_CLASS}
127
- `.trim().replace(/\s+/g, " ");
293
+ `).replace(/\s+/g, " ");
128
294
  });
129
295
  </script>
130
296
 
131
297
  {#if mounted}
132
298
  <button
133
299
  onclick={toggleTheme}
134
- class={buttonClasses()}
135
- aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
136
- aria-pressed={isDark}
300
+ class="{buttonClasses()} {className}"
301
+ aria-label={accessibleName}
302
+ aria-pressed={modes === "three" ? undefined : isDark}
303
+ data-theme-mode={modes === "three" ? pageMode : undefined}
137
304
  type="button"
138
305
  {disabled}
139
306
  {...restProps}
140
307
  >
141
- {#if isDark}
308
+ {#if modes === "three" && pageMode === "auto"}
309
+ <Monitor size={sizeClass().icon} class="text-label" />
310
+ {:else if modes === "three" ? pageMode === "dark" : isDark}
142
311
  <Moon size={sizeClass().icon} class="text-label" />
143
312
  {:else}
144
313
  <Sun size={sizeClass().icon} class="text-label" />
@@ -146,12 +315,25 @@
146
315
  </button>
147
316
  {:else}
148
317
  <button
149
- class="w-10 h-10 bg-base-50 rounded-lg flex items-center justify-center text-label cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
150
- aria-label="Theme toggle"
318
+ class="{sizeClass().button} {variantClass()} rounded-control flex items-center justify-center text-label cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
319
+ aria-label={text.beforeMount}
151
320
  type="button"
152
321
  {disabled}
153
322
  {...restProps}
154
323
  >
155
- <Sun size={20} class="text-label" />
324
+ <!-- Before mount there is no state to read: the server cannot know the
325
+ theme, and hydration has not run yet. Every icon is in the markup and
326
+ the stylesheet picks one, so a dark page never shows the Sun first:
327
+ by the `dark` class, by `data-theme="dark"`, or by `data-theme="auto"`
328
+ (the moon on a dark system with two modes, the monitor with three).
329
+ The icon matches the mounted button. -->
330
+ {#if modes === "three"}
331
+ <Sun size={sizeClass().icon} class="text-label {SUN_BEFORE_MOUNT_THREE}" />
332
+ <Moon size={sizeClass().icon} class="text-label {MOON_BEFORE_MOUNT_THREE}" />
333
+ <Monitor size={sizeClass().icon} class="text-label {SYSTEM_BEFORE_MOUNT_THREE}" />
334
+ {:else}
335
+ <Sun size={sizeClass().icon} class="text-label {SUN_BEFORE_MOUNT}" />
336
+ <Moon size={sizeClass().icon} class="text-label {MOON_BEFORE_MOUNT}" />
337
+ {/if}
156
338
  </button>
157
339
  {/if}
@@ -1,9 +1,34 @@
1
- interface Props {
1
+ import type { HTMLButtonAttributes } from "svelte/elements";
2
+ import { type ThemeMode, type ThemeToggleLabels } from "../util/theme-mode.js";
3
+ /** Other attributes (`data-*`, `aria-*`, `id`, ...) land on the `<button>`. */
4
+ type Props = Omit<HTMLButtonAttributes, "class" | "disabled" | "type" | "onclick" | "children"> & {
5
+ /** Extra classes for the host element. */
6
+ class?: string;
2
7
  size?: "sm" | "md" | "lg";
3
8
  variant?: "default" | "ghost" | "outline";
4
9
  disabled?: boolean;
10
+ /**
11
+ * `"two"` (the default) flips light and dark. `"three"` steps through
12
+ * system, light and dark, and always writes `data-theme` on `<html>`.
13
+ */
14
+ modes?: "two" | "three";
15
+ /**
16
+ * The page's mode: `"auto"` (follow the system), `"light"` or `"dark"`.
17
+ * Bindable. It is read from `<html>` when the button mounts and follows
18
+ * the page from then on; assigning it switches the page.
19
+ */
20
+ mode?: ThemeMode;
21
+ /** Called with the new mode when a press changes it. */
22
+ onmodechange?: (mode: ThemeMode) => void;
23
+ /**
24
+ * The `localStorage` key the choice is kept under and restored from when
25
+ * the button mounts. `null` keeps nothing.
26
+ */
27
+ storageKey?: string | null;
28
+ /** Replaces the texts of the accessible name, for another language. */
29
+ labels?: Partial<ThemeToggleLabels>;
5
30
  onclick?: (event: Event) => void;
6
- }
7
- declare const ThemeToggle: import("svelte").Component<Props, {}, "">;
31
+ };
32
+ declare const ThemeToggle: import("svelte").Component<Props, {}, "mode">;
8
33
  type ThemeToggle = ReturnType<typeof ThemeToggle>;
9
34
  export default ThemeToggle;