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,28 +1,99 @@
1
1
  <script lang="ts">
2
+ import { mergeStrings } from "../util/ready-made-strings.js";
3
+ import { zabiStringsFor } from "../util/zabi-strings.js";
2
4
  import Input from "./Input.svelte";
3
- import { onMount, onDestroy, tick } from "svelte";
5
+ import { onMount, tick } from "svelte";
6
+ import { isInsideToastRegion } from "../util/focus-utils.js";
7
+ import {
8
+ measurePlacement,
9
+ watchViewport,
10
+ type PanelPlacement,
11
+ } from "../util/fit-in-viewport.js";
12
+ import {
13
+ DEFAULT_COLOR_PICKER_STRINGS,
14
+ type ColorPickerStrings,
15
+ } from "../util/ready-made-strings.js";
4
16
 
5
17
  interface Props {
18
+ /** Extra classes for the host element. */
19
+ class?: string;
6
20
  value?: string;
7
21
  label?: string;
8
22
  disabled?: boolean;
9
23
  placeholder?: string;
24
+ /** The accessible names of its parts, for another language. */
25
+ strings?: Partial<ColorPickerStrings>;
10
26
  onchange?: (event: Event) => void;
11
27
  }
12
28
 
13
29
  let {
14
- value = "",
30
+ class: className = "",
31
+ value = $bindable<Exclude<Props["value"], undefined>>(),
15
32
  label = "",
16
33
  disabled = false,
17
34
  placeholder = "#000000",
35
+ strings,
18
36
  onchange,
19
37
  ...restProps
20
38
  }: Props = $props();
21
39
 
40
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
41
+ // one that has a fallback (`props_invalid_value`), and a page that throws
42
+ // while it hydrates never becomes interactive. The default is applied
43
+ // here instead: at once, for the server and the first render, and again
44
+ // whenever a parent hands back `undefined`.
45
+ const applyDefaults = () => {
46
+ if (value === undefined) value = "";
47
+ };
48
+ applyDefaults();
49
+ $effect.pre(applyDefaults);
50
+
51
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
52
+ const provided = zabiStringsFor("colorPicker");
53
+ const text = $derived(mergeStrings(DEFAULT_COLOR_PICKER_STRINGS, provided(), strings));
54
+
22
55
  let isOpen = $state(false);
23
- let isDragging = $state(false);
56
+ /** The pointer that is down on the colour map, if one is. */
57
+ let dragPointer: number | null = null;
24
58
  let pickerContainer: HTMLDivElement | undefined;
25
- let colorMap: HTMLCanvasElement | undefined;
59
+
60
+ /**
61
+ * Where the popover is. It hangs from the right edge of the swatch and is
62
+ * 20rem wide, so with the swatch in the left half of a phone screen it ran
63
+ * off the left edge. It is slid back to 8px from the edge, narrowed when
64
+ * it is wider than the screen, and placed against the viewport when a box
65
+ * that scrolls around it would cut it off (`fixed`). A popover that fits
66
+ * is not touched.
67
+ */
68
+ let popoverElement = $state<HTMLDivElement | null>(null);
69
+ let placed = $state<PanelPlacement | null>(null);
70
+
71
+ $effect(() => {
72
+ const panel = popoverElement;
73
+ const anchor = panel?.parentElement;
74
+ if (!isOpen || !panel || !anchor) {
75
+ placed = null;
76
+ return;
77
+ }
78
+ const update = () => {
79
+ // `top-12` is 3rem from the top of the box around the swatch,
80
+ // which can be a little taller than the swatch: the gap is what
81
+ // is left under that box, so the popover is where the class puts it.
82
+ const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
83
+ const gap = rem * 3 - anchor.getBoundingClientRect().height;
84
+ placed = measurePlacement(
85
+ anchor,
86
+ panel,
87
+ { block: "bottom", inline: "end" },
88
+ // Placed with `right`, in either writing direction, and always below.
89
+ { margin: 8, gap, flipBlock: false, flipInline: false, rtl: false },
90
+ );
91
+ };
92
+ update();
93
+ return watchViewport(update, panel);
94
+ });
95
+ let colorMap = $state<HTMLCanvasElement | undefined>();
96
+ let swatchButton: HTMLButtonElement | undefined;
26
97
  let colorMapContainer: HTMLDivElement | undefined;
27
98
 
28
99
  // Working color in HSL (h: 0–360; s, l: 0–100).
@@ -35,7 +106,14 @@
35
106
  return hexPattern.test(hex);
36
107
  }
37
108
 
38
- function hexToHsl(hex: string): [number, number, number] {
109
+ /** `#f00` → `#ff0000`; 6-digit input is returned unchanged. */
110
+ function expandHex(hex: string): string {
111
+ if (hex.length !== 4) return hex;
112
+ return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
113
+ }
114
+
115
+ function hexToHsl(rawHex: string): [number, number, number] {
116
+ const hex = expandHex(rawHex);
39
117
  const r = parseInt(hex.slice(1, 3), 16) / 255;
40
118
  const g = parseInt(hex.slice(3, 5), 16) / 255;
41
119
  const b = parseInt(hex.slice(5, 7), 16) / 255;
@@ -98,9 +176,7 @@
98
176
  const variant = $derived(value && !isValidHex(value) ? "error" : "default");
99
177
 
100
178
  const message = $derived(
101
- value && !isValidHex(value)
102
- ? "Please enter a valid hex color (e.g., #ff0000 or #f00)"
103
- : "",
179
+ value && !isValidHex(value) ? text.invalidHex : "",
104
180
  );
105
181
 
106
182
  function handleInput(event: Event) {
@@ -132,7 +208,7 @@
132
208
  commitCurrentValue(event);
133
209
  }
134
210
 
135
- async function togglePicker() {
211
+ async function togglePicker(event?: MouseEvent) {
136
212
  if (!disabled) {
137
213
  if (isOpen) {
138
214
  commitCurrentValue(new Event("change"));
@@ -141,13 +217,41 @@
141
217
  if (isOpen && value && isValidHex(value)) {
142
218
  updateFromHex(value);
143
219
  }
144
- if (isOpen) {
220
+ // A click from the keyboard has no pointer (`detail` is 0): focus
221
+ // goes in, to the first stop of the popover. A press keeps it.
222
+ if (isOpen && event && event.detail === 0) {
145
223
  await tick();
146
- drawColorMap();
224
+ saturationInput?.focus({ preventScroll: true });
147
225
  }
148
226
  }
149
227
  }
150
228
 
229
+ /** Escape inside the popover, or on the swatch while it is open: close, and back to the swatch. */
230
+ function handleEscape(event: KeyboardEvent) {
231
+ if (event.key !== "Escape" || !isOpen) return;
232
+ // Consumed here: a Modal or Drawer around the picker stays open.
233
+ event.stopPropagation();
234
+ event.preventDefault();
235
+ commitCurrentValue(new Event("change"));
236
+ isOpen = false;
237
+ swatchButton?.focus({ preventScroll: true });
238
+ }
239
+
240
+ // The map is drawn for the hue it is open at, and again for any change of
241
+ // it: the hue slider, a typed hex, or a `value` set from outside.
242
+ $effect(() => {
243
+ void hue;
244
+ if (isOpen && colorMap) drawColorMap();
245
+ });
246
+
247
+ // A `value` changed from outside while open (not the one the sliders just made).
248
+ $effect(() => {
249
+ if (!isOpen || !value || !isValidHex(value)) return;
250
+ if (expandHex(value).toLowerCase() !== hslToHex(hue, saturation, lightness)) {
251
+ updateFromHex(value);
252
+ }
253
+ });
254
+
151
255
  function drawColorMap() {
152
256
  if (!colorMap) return;
153
257
  const ctx = colorMap.getContext("2d");
@@ -167,34 +271,93 @@
167
271
  }
168
272
  }
169
273
 
170
- function handleColorMapClick(event: MouseEvent) {
171
- if (!colorMapContainer || !colorMap) return;
274
+ /**
275
+ * The colour map: saturation across, lightness up.
276
+ *
277
+ * It is two sliders on one surface, and is built as that: two visually
278
+ * hidden `<input type="range">`, which is what a screen reader, a switch
279
+ * and a voice command already know how to name, read and set ("Saturation,
280
+ * slider, 50%"). A single element cannot say it: `role="slider"` has one
281
+ * value, and an unnamed `role="button"`, which this was, has none.
282
+ *
283
+ * For the keyboard the two act as one map. Left and right move
284
+ * saturation and up and down lightness, from whichever slider has focus,
285
+ * and focus goes to the one that moved, so its new value is what is
286
+ * read out. Shift moves by ten. Only the first is a Tab stop.
287
+ *
288
+ * The surface takes pointer events, so a finger and a pen work as a mouse
289
+ * does; it used to listen for `mousedown` alone. The whole surface is the
290
+ * target (the mark on it is not something to hit), and `touch-none`
291
+ * keeps a drag on it from scrolling the page.
292
+ */
293
+ let saturationInput = $state<HTMLInputElement | null>(null);
294
+ let lightnessInput = $state<HTMLInputElement | null>(null);
295
+
296
+ const percent = (value: number) => Math.max(0, Math.min(100, Math.round(value)));
297
+
298
+ function setFromPoint(event: PointerEvent) {
299
+ if (!colorMapContainer) return;
172
300
  const rect = colorMapContainer.getBoundingClientRect();
173
- const x = event.clientX - rect.left;
174
- const y = event.clientY - rect.top;
301
+ if (rect.width === 0 || rect.height === 0) return;
302
+ saturation = percent(((event.clientX - rect.left) / rect.width) * 100);
303
+ lightness = percent(100 - ((event.clientY - rect.top) / rect.height) * 100);
304
+ updateHexFromHsl();
305
+ }
175
306
 
176
- saturation = Math.max(0, Math.min(100, (x / rect.width) * 100));
177
- lightness = Math.max(0, Math.min(100, 100 - (y / rect.height) * 100));
307
+ function handleMapPointerDown(event: PointerEvent) {
308
+ if (event.pointerType === "mouse" && event.button !== 0) return;
309
+ dragPointer = event.pointerId;
310
+ try {
311
+ colorMapContainer?.setPointerCapture(event.pointerId);
312
+ } catch {
313
+ /* a test DOM, or a pointer that is already gone */
314
+ }
315
+ setFromPoint(event);
316
+ // The keys carry on from where the pointer left it.
317
+ saturationInput?.focus({ preventScroll: true });
318
+ // Not a text selection, and not a second focus change from the press itself.
319
+ event.preventDefault();
320
+ }
178
321
 
179
- updateHexFromHsl();
180
- drawColorMap();
322
+ function handleMapPointerMove(event: PointerEvent) {
323
+ if (dragPointer !== event.pointerId) return;
324
+ setFromPoint(event);
181
325
  }
182
326
 
183
- function handleColorMapDrag(event: MouseEvent) {
184
- if (isDragging) {
185
- handleColorMapClick(event);
327
+ function handleMapPointerUp(event: PointerEvent) {
328
+ if (dragPointer !== event.pointerId) return;
329
+ dragPointer = null;
330
+ }
331
+
332
+ function handleMapKeydown(event: KeyboardEvent) {
333
+ const step = event.shiftKey ? 10 : 1;
334
+ const across = event.key === "ArrowRight" ? step : event.key === "ArrowLeft" ? -step : 0;
335
+ const up = event.key === "ArrowUp" ? step : event.key === "ArrowDown" ? -step : 0;
336
+ if (across === 0 && up === 0) return;
337
+ // The slider's own handling would move the focused one for all four keys.
338
+ event.preventDefault();
339
+ if (across !== 0) {
340
+ saturation = percent(saturation + across);
341
+ saturationInput?.focus({ preventScroll: true });
342
+ } else {
343
+ lightness = percent(lightness + up);
344
+ lightnessInput?.focus({ preventScroll: true });
186
345
  }
346
+ updateHexFromHsl();
187
347
  }
188
348
 
189
- function handleMouseUp() {
190
- isDragging = false;
349
+ /** Home, End, Page Up and Down, and what assistive technology sets: the slider's own value. */
350
+ function handleMapInput(axis: "saturation" | "lightness", event: Event) {
351
+ const next = percent(Number((event.currentTarget as HTMLInputElement).value));
352
+ if (axis === "saturation") saturation = next;
353
+ else lightness = next;
354
+ updateHexFromHsl();
191
355
  }
192
356
 
193
357
  function handleHueChange(event: Event) {
194
358
  const target = event.target as HTMLInputElement;
195
359
  hue = parseInt(target.value);
196
360
  updateHexFromHsl();
197
- drawColorMap();
198
361
  }
199
362
 
200
363
  function setPickerContainer(node: HTMLDivElement) {
@@ -231,11 +394,13 @@
231
394
  }
232
395
 
233
396
  function handleClickOutside(event: MouseEvent) {
397
+ // A toast lies over the panel; a press on it is for the toast.
398
+ if (isInsideToastRegion(event.target)) return;
234
399
  if (
235
400
  pickerContainer &&
236
401
  !pickerContainer.contains(event.target as Node) &&
237
402
  !(event.target as HTMLElement).closest(
238
- '[aria-label="Open color picker"]',
403
+ "[data-color-picker-open]",
239
404
  )
240
405
  ) {
241
406
  commitCurrentValue(new Event("change"));
@@ -248,22 +413,25 @@
248
413
  updateFromHex(value);
249
414
  }
250
415
  window.addEventListener("mousedown", handleClickOutside);
251
- window.addEventListener("mouseup", handleMouseUp);
252
- window.addEventListener("mousemove", handleColorMapDrag);
253
- });
254
416
 
255
- onDestroy(() => {
256
- window.removeEventListener("mousedown", handleClickOutside);
257
- window.removeEventListener("mouseup", handleMouseUp);
258
- window.removeEventListener("mousemove", handleColorMapDrag);
417
+ // Teardown is returned from onMount rather than registered with
418
+ // onDestroy: onDestroy also runs on the server, where there is no window.
419
+ return () => {
420
+ window.removeEventListener("mousedown", handleClickOutside);
421
+ };
259
422
  });
260
423
 
261
424
  const displayColor = $derived(value && isValidHex(value) ? value : placeholder);
262
425
  const pickerIndicatorX = $derived(`${saturation}%`);
263
426
  const pickerIndicatorY = $derived(`${100 - lightness}%`);
427
+ /** What a slider of the map says: its own value, and the colour the two make. */
428
+ const mapValueText = (value: number) => `${Math.round(value)}%, ${hslToHex(hue, saturation, lightness)}`;
429
+ /** A ring around a surface whose slider has keyboard focus: the sliders themselves are not drawn. */
430
+ const RING_WITHIN =
431
+ "has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-focus-ring";
264
432
  </script>
265
433
 
266
- <div {...restProps}>
434
+ <div class={className} {...restProps}>
267
435
  <div class="flex items-start gap-2">
268
436
  <div class="flex-1">
269
437
  <Input
@@ -275,7 +443,7 @@
275
443
  {message}
276
444
  oninput={handleInput}
277
445
  onblur={handleBlur}
278
- aria-label="Hex color input"
446
+ aria-label={label ? undefined : text.hexInput}
279
447
  />
280
448
  </div>
281
449
  <div class="relative shrink-0 mt-6">
@@ -283,48 +451,100 @@
283
451
  type="button"
284
452
  onclick={togglePicker}
285
453
  {disabled}
286
- class="focus-ring w-11 h-11 rounded-lg border-2 border-card shrink-0 cursor-pointer hover:ring-2 hover:ring-border transition-colors disabled:cursor-not-allowed disabled:opacity-50"
454
+ class="focus-ring w-11 h-11 rounded-control border-2 border-card shrink-0 cursor-pointer hover:ring-2 hover:ring-border active:ring-2 active:ring-border-strong transition-colors disabled:cursor-not-allowed disabled:opacity-50"
287
455
  style="background-color: {displayColor};"
288
- aria-label="Open color picker"
456
+ aria-label={label ? `${label}, ${text.open}` : text.open}
457
+ bind:this={swatchButton}
458
+ onkeydown={handleEscape}
289
459
  aria-expanded={isOpen}
460
+ data-color-picker-open
290
461
  ></button>
291
462
 
292
463
  {#if isOpen}
293
464
  <div
294
465
  use:setPickerContainer
295
- class="absolute top-12 right-0 z-50 border border-input-border bg-input rounded-2xl shadow-lg p-4 w-80"
466
+ bind:this={popoverElement}
467
+ class="absolute top-12 right-0 z-popover border border-border-overlay bg-surface-overlay rounded-overlay shadow-lg p-4 w-80"
468
+ style:position={placed?.fixed ? "fixed" : undefined}
469
+ style:top={placed?.fixed ? `${placed.fixed.top}px` : undefined}
470
+ style:left={placed?.fixed ? `${placed.fixed.left}px` : undefined}
471
+ style:right={placed?.fixed
472
+ ? "auto"
473
+ : placed && placed.inlineOffset !== 0
474
+ ? `${placed.inlineOffset}px`
475
+ : undefined}
476
+ style:width={placed?.fixed ? `${placed.fixed.width}px` : undefined}
477
+ style:max-width={placed && placed.maxWidth !== null ? `${placed.maxWidth}px` : undefined}
296
478
  role="dialog"
297
- aria-label="Color picker"
479
+ aria-label={text.picker}
480
+ tabindex="-1"
481
+ onkeydown={handleEscape}
298
482
  >
299
483
  <div class="space-y-4">
484
+ <!-- The sliders inside are the controls; the pointer
485
+ handlers are on the surface they share. -->
486
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
300
487
  <div
301
488
  use:setColorMapContainer
302
- class="relative w-full h-48 rounded-xl overflow-hidden cursor-crosshair"
303
- onmousedown={(e) => {
304
- isDragging = true;
305
- handleColorMapClick(e);
306
- }}
307
- role="button"
308
- tabindex="0"
489
+ class="relative w-full h-48 rounded-container cursor-crosshair touch-none select-none {RING_WITHIN}"
490
+ role="group"
491
+ aria-label={text.area}
492
+ onpointerdown={handleMapPointerDown}
493
+ onpointermove={handleMapPointerMove}
494
+ onpointerup={handleMapPointerUp}
495
+ onpointercancel={handleMapPointerUp}
309
496
  >
310
497
  <canvas
311
498
  use:setColorMap
312
499
  width={256}
313
500
  height={192}
314
- class="w-full h-full"
501
+ class="w-full h-full rounded-container"
315
502
  ></canvas>
503
+ <input
504
+ bind:this={saturationInput}
505
+ type="range"
506
+ min="0"
507
+ max="100"
508
+ step="1"
509
+ value={saturation}
510
+ class="sr-only"
511
+ aria-label={text.saturation}
512
+ aria-valuetext={mapValueText(saturation)}
513
+ oninput={(event) => handleMapInput("saturation", event)}
514
+ onkeydown={handleMapKeydown}
515
+ />
516
+ <input
517
+ bind:this={lightnessInput}
518
+ type="range"
519
+ min="0"
520
+ max="100"
521
+ step="1"
522
+ value={lightness}
523
+ tabindex="-1"
524
+ class="sr-only"
525
+ aria-label={text.lightness}
526
+ aria-orientation="vertical"
527
+ aria-valuetext={mapValueText(lightness)}
528
+ oninput={(event) => handleMapInput("lightness", event)}
529
+ onkeydown={handleMapKeydown}
530
+ />
531
+ <!-- White with a dark edge: seen on the white corner of the map as on the black. -->
316
532
  <div
317
- class="absolute w-4 h-4 border-2 border-white rounded-full shadow-lg pointer-events-none transform -translate-x-1/2 -translate-y-1/2"
533
+ class="absolute w-4 h-4 border-2 border-white rounded-full shadow-[0_0_0_1px_rgba(0,0,0,0.6)] pointer-events-none transform -translate-x-1/2 -translate-y-1/2"
318
534
  style="left: {pickerIndicatorX}; top: {pickerIndicatorY};"
319
535
  ></div>
320
536
  </div>
321
537
 
322
538
  <div class="space-y-2">
539
+ <!-- A real slider, drawn by the two layers around
540
+ it. It is transparent, so the ring for keyboard
541
+ focus is on this box; and the box is a finger tall
542
+ on a touch screen, where 24px was not. -->
323
543
  <div
324
- class="relative h-6 rounded-lg overflow-hidden"
544
+ class="relative h-6 pointer-coarse:h-11 rounded-control {RING_WITHIN}"
325
545
  >
326
546
  <div
327
- class="absolute inset-0"
547
+ class="absolute inset-0 rounded-control"
328
548
  style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%));"
329
549
  ></div>
330
550
  <input
@@ -334,10 +554,10 @@
334
554
  value={hue}
335
555
  oninput={handleHueChange}
336
556
  class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
337
- aria-label="Hue slider"
557
+ aria-label={text.hue}
338
558
  />
339
559
  <div
340
- class="absolute top-0 bottom-0 w-3 bg-card shadow-md pointer-events-none my-0.25 rounded-full border border-base-950"
560
+ class="absolute top-0 bottom-0 w-3 bg-card shadow-sm pointer-events-none my-px rounded-full border border-base-950"
341
561
  style="left: {(hue / 360) * 100}%;"
342
562
  ></div>
343
563
  </div>
@@ -1,10 +1,15 @@
1
+ import { type ColorPickerStrings } from "../util/ready-made-strings.js";
1
2
  interface Props {
3
+ /** Extra classes for the host element. */
4
+ class?: string;
2
5
  value?: string;
3
6
  label?: string;
4
7
  disabled?: boolean;
5
8
  placeholder?: string;
9
+ /** The accessible names of its parts, for another language. */
10
+ strings?: Partial<ColorPickerStrings>;
6
11
  onchange?: (event: Event) => void;
7
12
  }
8
- declare const ColorPicker: import("svelte").Component<Props, {}, "">;
13
+ declare const ColorPicker: import("svelte").Component<Props, {}, "value">;
9
14
  type ColorPicker = ReturnType<typeof ColorPicker>;
10
15
  export default ColorPicker;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
 
4
+ import { cn } from "../util/cn.js";
4
5
  type MaxWidth = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
5
6
 
6
7
  interface Props {
@@ -27,15 +28,21 @@
27
28
  '2xl': 'max-w-screen-2xl',
28
29
  full: 'max-w-full',
29
30
  };
31
+
32
+ const classes = $derived(
33
+ cn('mx-auto w-full', maxClass[maxWidth], padded ? 'px-4 sm:px-6' : '', className),
34
+ );
30
35
  </script>
31
36
 
32
- <svelte:element
33
- this={Tag}
34
- class={['mx-auto w-full', maxClass[maxWidth], padded ? 'px-4 sm:px-6' : '', className]
35
- .filter(Boolean)
36
- .join(' ')}
37
- >
38
- {#if children}
39
- {@render children()}
40
- {/if}
41
- </svelte:element>
37
+ <!-- One branch per tag, not `<svelte:element>`: hydration takes a dynamic
38
+ element out and puts it back, which blurs a control inside it that the user
39
+ had already tabbed to. -->
40
+ {#if Tag === 'section'}
41
+ <section class={classes}>{@render children?.()}</section>
42
+ {:else if Tag === 'main'}
43
+ <main class={classes}>{@render children?.()}</main>
44
+ {:else if Tag === 'article'}
45
+ <article class={classes}>{@render children?.()}</article>
46
+ {:else}
47
+ <div class={classes}>{@render children?.()}</div>
48
+ {/if}
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { SizeVariant } from "../types/variants.js";
4
+ import TemporalField from "./TemporalField.svelte";
5
+
6
+ /**
7
+ * A date, picked with the device's own date picker and styled like Input.
8
+ *
9
+ * It is a native `<input type="date">`: an iPhone shows its wheels or
10
+ * calendar, Android its calendar, a desktop browser its own. `value` is
11
+ * always `YYYY-MM-DD`, or `""` while empty, whatever the field shows.
12
+ *
13
+ * What the field shows is the browser's decision: the date in the format
14
+ * of the browser or device, not of the page, and no attribute changes
15
+ * that. To show a date as text in the page's language, use `formatDate`:
16
+ *
17
+ * ```svelte
18
+ * <DateField label="Date" bind:value={date} min="2026-01-01" />
19
+ * <p>{formatDate(date, "sv")}</p> <!-- 6 okt. 2026 -->
20
+ * ```
21
+ *
22
+ * Other attributes (`autocomplete`, `data-*`, `onchange`, ...) land on the
23
+ * `<input>`.
24
+ */
25
+ type Props = Omit<
26
+ HTMLInputAttributes,
27
+ "class" | "type" | "value" | "size" | "min" | "max" | "step" | "required" | "disabled" | "readonly" | "id" | "name"
28
+ > & {
29
+ /** Omit to auto-generate; pair with FormField's `id` when used inside FormField. */
30
+ id?: string;
31
+ /** The date as `YYYY-MM-DD`, or `""` while empty. Supports `bind:value`. */
32
+ value?: string;
33
+ /** Name the value is submitted under in a form. */
34
+ name?: string;
35
+ label?: string;
36
+ /** True when FormField supplies the visible `<label>`. */
37
+ hideLabel?: boolean;
38
+ /** Help text under the field, read out with it. */
39
+ hint?: string;
40
+ /** An error under the field. It marks the field invalid and is announced. */
41
+ error?: string;
42
+ /** Earliest date that can be picked, as `YYYY-MM-DD`. */
43
+ min?: string;
44
+ /** Latest date that can be picked, as `YYYY-MM-DD`. */
45
+ max?: string;
46
+ /** Steps between dates that can be picked, in days. */
47
+ step?: number | "any";
48
+ required?: boolean;
49
+ disabled?: boolean;
50
+ /** Shows the value without letting it be changed; it is still submitted. */
51
+ readonly?: boolean;
52
+ /** Height, on the scale Input and Button use. At least 44px on a touch screen. */
53
+ size?: SizeVariant;
54
+ /** Extra classes for the `<input>`. */
55
+ class?: string;
56
+ };
57
+
58
+ let {
59
+ id,
60
+ value = $bindable<Exclude<Props["value"], undefined>>(),
61
+ name = "",
62
+ label = "",
63
+ hideLabel = false,
64
+ hint = "",
65
+ error = "",
66
+ min,
67
+ max,
68
+ step,
69
+ required = false,
70
+ disabled = false,
71
+ readonly = false,
72
+ size = "md",
73
+ class: className = "",
74
+ ...restProps
75
+ }: Props = $props();
76
+
77
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
78
+ // one that has a fallback (`props_invalid_value`), and a page that throws
79
+ // while it hydrates never becomes interactive. The default is applied
80
+ // here instead: at once, for the server and the first render, and again
81
+ // whenever a parent hands back `undefined`.
82
+ const applyDefaults = () => {
83
+ if (value === undefined) value = "";
84
+ };
85
+ applyDefaults();
86
+ $effect.pre(applyDefaults);
87
+ </script>
88
+
89
+ <TemporalField
90
+ kind="date"
91
+ {id}
92
+ bind:value
93
+ {name}
94
+ {label}
95
+ {hideLabel}
96
+ {hint}
97
+ {error}
98
+ {min}
99
+ {max}
100
+ {step}
101
+ {required}
102
+ {disabled}
103
+ {readonly}
104
+ {size}
105
+ class={className}
106
+ {...restProps}
107
+ />