zabi-components 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -1,6 +1,18 @@
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 {
6
18
  /** Extra classes for the host element. */
@@ -9,23 +21,79 @@
9
21
  label?: string;
10
22
  disabled?: boolean;
11
23
  placeholder?: string;
24
+ /** The accessible names of its parts, for another language. */
25
+ strings?: Partial<ColorPickerStrings>;
12
26
  onchange?: (event: Event) => void;
13
27
  }
14
28
 
15
29
  let {
16
30
  class: className = "",
17
- value = $bindable(""),
31
+ value = $bindable<Exclude<Props["value"], undefined>>(),
18
32
  label = "",
19
33
  disabled = false,
20
34
  placeholder = "#000000",
35
+ strings,
21
36
  onchange,
22
37
  ...restProps
23
38
  }: Props = $props();
24
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
+
25
55
  let isOpen = $state(false);
26
- let isDragging = $state(false);
56
+ /** The pointer that is down on the colour map, if one is. */
57
+ let dragPointer: number | null = null;
27
58
  let pickerContainer: HTMLDivElement | undefined;
28
- 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;
29
97
  let colorMapContainer: HTMLDivElement | undefined;
30
98
 
31
99
  // Working color in HSL (h: 0–360; s, l: 0–100).
@@ -108,9 +176,7 @@
108
176
  const variant = $derived(value && !isValidHex(value) ? "error" : "default");
109
177
 
110
178
  const message = $derived(
111
- value && !isValidHex(value)
112
- ? "Please enter a valid hex color (e.g., #ff0000 or #f00)"
113
- : "",
179
+ value && !isValidHex(value) ? text.invalidHex : "",
114
180
  );
115
181
 
116
182
  function handleInput(event: Event) {
@@ -142,7 +208,7 @@
142
208
  commitCurrentValue(event);
143
209
  }
144
210
 
145
- async function togglePicker() {
211
+ async function togglePicker(event?: MouseEvent) {
146
212
  if (!disabled) {
147
213
  if (isOpen) {
148
214
  commitCurrentValue(new Event("change"));
@@ -151,13 +217,41 @@
151
217
  if (isOpen && value && isValidHex(value)) {
152
218
  updateFromHex(value);
153
219
  }
154
- 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) {
155
223
  await tick();
156
- drawColorMap();
224
+ saturationInput?.focus({ preventScroll: true });
157
225
  }
158
226
  }
159
227
  }
160
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
+
161
255
  function drawColorMap() {
162
256
  if (!colorMap) return;
163
257
  const ctx = colorMap.getContext("2d");
@@ -177,34 +271,93 @@
177
271
  }
178
272
  }
179
273
 
180
- function handleColorMapClick(event: MouseEvent) {
181
- 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;
182
300
  const rect = colorMapContainer.getBoundingClientRect();
183
- const x = event.clientX - rect.left;
184
- 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
+ }
185
306
 
186
- saturation = Math.max(0, Math.min(100, (x / rect.width) * 100));
187
- 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
+ }
188
321
 
189
- updateHexFromHsl();
190
- drawColorMap();
322
+ function handleMapPointerMove(event: PointerEvent) {
323
+ if (dragPointer !== event.pointerId) return;
324
+ setFromPoint(event);
191
325
  }
192
326
 
193
- function handleColorMapDrag(event: MouseEvent) {
194
- if (isDragging) {
195
- 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 });
196
345
  }
346
+ updateHexFromHsl();
197
347
  }
198
348
 
199
- function handleMouseUp() {
200
- 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();
201
355
  }
202
356
 
203
357
  function handleHueChange(event: Event) {
204
358
  const target = event.target as HTMLInputElement;
205
359
  hue = parseInt(target.value);
206
360
  updateHexFromHsl();
207
- drawColorMap();
208
361
  }
209
362
 
210
363
  function setPickerContainer(node: HTMLDivElement) {
@@ -241,11 +394,13 @@
241
394
  }
242
395
 
243
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;
244
399
  if (
245
400
  pickerContainer &&
246
401
  !pickerContainer.contains(event.target as Node) &&
247
402
  !(event.target as HTMLElement).closest(
248
- '[aria-label="Open color picker"]',
403
+ "[data-color-picker-open]",
249
404
  )
250
405
  ) {
251
406
  commitCurrentValue(new Event("change"));
@@ -258,19 +413,22 @@
258
413
  updateFromHex(value);
259
414
  }
260
415
  window.addEventListener("mousedown", handleClickOutside);
261
- window.addEventListener("mouseup", handleMouseUp);
262
- window.addEventListener("mousemove", handleColorMapDrag);
263
- });
264
416
 
265
- onDestroy(() => {
266
- window.removeEventListener("mousedown", handleClickOutside);
267
- window.removeEventListener("mouseup", handleMouseUp);
268
- 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
+ };
269
422
  });
270
423
 
271
424
  const displayColor = $derived(value && isValidHex(value) ? value : placeholder);
272
425
  const pickerIndicatorX = $derived(`${saturation}%`);
273
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";
274
432
  </script>
275
433
 
276
434
  <div class={className} {...restProps}>
@@ -285,7 +443,7 @@
285
443
  {message}
286
444
  oninput={handleInput}
287
445
  onblur={handleBlur}
288
- aria-label="Hex color input"
446
+ aria-label={label ? undefined : text.hexInput}
289
447
  />
290
448
  </div>
291
449
  <div class="relative shrink-0 mt-6">
@@ -293,48 +451,101 @@
293
451
  type="button"
294
452
  onclick={togglePicker}
295
453
  {disabled}
296
- class="focus-ring w-11 h-11 rounded-control 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"
297
455
  style="background-color: {displayColor};"
298
- aria-label="Open color picker"
456
+ aria-label={label ? `${label}, ${text.open}` : text.open}
457
+ bind:this={swatchButton}
458
+ onkeydown={handleEscape}
299
459
  aria-expanded={isOpen}
460
+ data-color-picker-open
300
461
  ></button>
301
462
 
302
463
  {#if isOpen}
303
464
  <div
304
465
  use:setPickerContainer
305
- class="absolute top-12 right-0 z-popover border border-input-border bg-input rounded-container 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}
306
478
  role="dialog"
307
- aria-label="Color picker"
479
+ aria-label={text.picker}
480
+ tabindex="-1"
481
+ onkeydown={handleEscape}
308
482
  >
309
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 -->
310
487
  <div
311
488
  use:setColorMapContainer
312
- class="relative w-full h-48 rounded-container overflow-hidden cursor-crosshair"
313
- onmousedown={(e) => {
314
- isDragging = true;
315
- handleColorMapClick(e);
316
- }}
317
- role="button"
318
- tabindex="0"
489
+ class="relative w-full h-48 rounded-container cursor-crosshair touch-none select-none {RING_WITHIN}"
490
+ role="group"
491
+ data-sheet-no-drag
492
+ aria-label={text.area}
493
+ onpointerdown={handleMapPointerDown}
494
+ onpointermove={handleMapPointerMove}
495
+ onpointerup={handleMapPointerUp}
496
+ onpointercancel={handleMapPointerUp}
319
497
  >
320
498
  <canvas
321
499
  use:setColorMap
322
500
  width={256}
323
501
  height={192}
324
- class="w-full h-full"
502
+ class="w-full h-full rounded-container"
325
503
  ></canvas>
504
+ <input
505
+ bind:this={saturationInput}
506
+ type="range"
507
+ min="0"
508
+ max="100"
509
+ step="1"
510
+ value={saturation}
511
+ class="sr-only"
512
+ aria-label={text.saturation}
513
+ aria-valuetext={mapValueText(saturation)}
514
+ oninput={(event) => handleMapInput("saturation", event)}
515
+ onkeydown={handleMapKeydown}
516
+ />
517
+ <input
518
+ bind:this={lightnessInput}
519
+ type="range"
520
+ min="0"
521
+ max="100"
522
+ step="1"
523
+ value={lightness}
524
+ tabindex="-1"
525
+ class="sr-only"
526
+ aria-label={text.lightness}
527
+ aria-orientation="vertical"
528
+ aria-valuetext={mapValueText(lightness)}
529
+ oninput={(event) => handleMapInput("lightness", event)}
530
+ onkeydown={handleMapKeydown}
531
+ />
532
+ <!-- White with a dark edge: seen on the white corner of the map as on the black. -->
326
533
  <div
327
- class="absolute w-4 h-4 border-2 border-white rounded-full shadow-sm pointer-events-none transform -translate-x-1/2 -translate-y-1/2"
534
+ 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"
328
535
  style="left: {pickerIndicatorX}; top: {pickerIndicatorY};"
329
536
  ></div>
330
537
  </div>
331
538
 
332
539
  <div class="space-y-2">
540
+ <!-- A real slider, drawn by the two layers around
541
+ it. It is transparent, so the ring for keyboard
542
+ focus is on this box; and the box is a finger tall
543
+ on a touch screen, where 24px was not. -->
333
544
  <div
334
- class="relative h-6 rounded-control overflow-hidden"
545
+ class="relative h-6 pointer-coarse:h-11 rounded-control {RING_WITHIN}"
335
546
  >
336
547
  <div
337
- class="absolute inset-0"
548
+ class="absolute inset-0 rounded-control"
338
549
  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%));"
339
550
  ></div>
340
551
  <input
@@ -344,7 +555,7 @@
344
555
  value={hue}
345
556
  oninput={handleHueChange}
346
557
  class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
347
- aria-label="Hue slider"
558
+ aria-label={text.hue}
348
559
  />
349
560
  <div
350
561
  class="absolute top-0 bottom-0 w-3 bg-card shadow-sm pointer-events-none my-px rounded-full border border-base-950"
@@ -1,3 +1,4 @@
1
+ import { type ColorPickerStrings } from "../util/ready-made-strings.js";
1
2
  interface Props {
2
3
  /** Extra classes for the host element. */
3
4
  class?: string;
@@ -5,6 +6,8 @@ interface Props {
5
6
  label?: string;
6
7
  disabled?: boolean;
7
8
  placeholder?: string;
9
+ /** The accessible names of its parts, for another language. */
10
+ strings?: Partial<ColorPickerStrings>;
8
11
  onchange?: (event: Event) => void;
9
12
  }
10
13
  declare const ColorPicker: import("svelte").Component<Props, {}, "value">;
@@ -28,13 +28,21 @@
28
28
  '2xl': 'max-w-screen-2xl',
29
29
  full: 'max-w-full',
30
30
  };
31
+
32
+ const classes = $derived(
33
+ cn('mx-auto w-full', maxClass[maxWidth], padded ? 'px-4 sm:px-6' : '', className),
34
+ );
31
35
  </script>
32
36
 
33
- <svelte:element
34
- this={Tag}
35
- class={cn('mx-auto w-full', maxClass[maxWidth], padded ? 'px-4 sm:px-6' : '', className)}
36
- >
37
- {#if children}
38
- {@render children()}
39
- {/if}
40
- </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,145 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { SizeVariant } from "../types/variants.js";
4
+ import type { DateFieldStrings } from "../util/temporal-field.js";
5
+ import TemporalField from "./TemporalField.svelte";
6
+
7
+ /**
8
+ * A date, picked with the device's own date picker and styled like Input.
9
+ *
10
+ * It is a native `<input type="date">`: an iPhone shows its wheels or
11
+ * calendar, Android its calendar, a desktop browser its own. `value` is
12
+ * always `YYYY-MM-DD`, or `""` while empty, whatever the field shows.
13
+ *
14
+ * What the field shows is the browser's decision, unless it has a
15
+ * `locale`: without one, the date is in the format of the browser or
16
+ * device, not of the page, and no attribute changes that. To show a date
17
+ * as text in the page's language, use `formatDate`:
18
+ *
19
+ * ```svelte
20
+ * <DateField label="Date" bind:value={date} min="2026-01-01" />
21
+ * <p>{formatDate(date, "sv")}</p> <!-- 6 okt. 2026 -->
22
+ * ```
23
+ *
24
+ * With `locale`, the field shows the value in that language and its own
25
+ * picker opens: a Calendar in a BottomSheet (at every width), with Clear
26
+ * unless the field is `required`. Until the page has run, and without
27
+ * scripts, it is the native input as above and submits as before; once
28
+ * it has, the native input stays as the form control, out of sight, and a
29
+ * button in its place shows the date. `picker="native"` opens the
30
+ * platform's own picker instead.
31
+ *
32
+ * ```svelte
33
+ * <DateField label="Datum" locale="sv" bind:value format={{ weekday: "short", day: "numeric", month: "short" }} />
34
+ * ```
35
+ *
36
+ * Other attributes (`autocomplete`, `data-*`, `onchange`, ...) land on the
37
+ * `<input>`.
38
+ */
39
+ type Props = Omit<
40
+ HTMLInputAttributes,
41
+ "class" | "type" | "value" | "size" | "min" | "max" | "step" | "required" | "disabled" | "readonly" | "id" | "name"
42
+ > & {
43
+ /** Omit to auto-generate; pair with FormField's `id` when used inside FormField. */
44
+ id?: string;
45
+ /** The date as `YYYY-MM-DD`, or `""` while empty. Supports `bind:value`. */
46
+ value?: string;
47
+ /** Name the value is submitted under in a form. */
48
+ name?: string;
49
+ label?: string;
50
+ /** True when FormField supplies the visible `<label>`. */
51
+ hideLabel?: boolean;
52
+ /** Help text under the field, read out with it. */
53
+ hint?: string;
54
+ /** An error under the field. It marks the field invalid and is announced. */
55
+ error?: string;
56
+ /** Earliest date that can be picked, as `YYYY-MM-DD`. */
57
+ min?: string;
58
+ /** Latest date that can be picked, as `YYYY-MM-DD`. */
59
+ max?: string;
60
+ /** Steps between dates that can be picked, in days. */
61
+ step?: number | "any";
62
+ required?: boolean;
63
+ disabled?: boolean;
64
+ /** Shows the value without letting it be changed; it is still submitted. */
65
+ readonly?: boolean;
66
+ /** Height, on the scale Input and Button use. At least 44px on a touch screen. */
67
+ size?: SizeVariant;
68
+ /** Extra classes for the `<input>`. */
69
+ class?: string;
70
+ /**
71
+ * Language of the field, as a BCP 47 tag (`sv`, `en-GB`) or a list of them. With it the
72
+ * field shows the value in that language, whatever the browser's is, and opens the
73
+ * library's own picker; see above. Left out, the field is the native input.
74
+ */
75
+ locale?: string | string[];
76
+ /** With `locale`: how the value is shown, as options of `Intl.DateTimeFormat`. Default: what formatDate gives for the locale. */
77
+ format?: Intl.DateTimeFormatOptions;
78
+ /** With `locale`: shown in the placeholder colour while the field is empty. */
79
+ placeholder?: string;
80
+ /** With `locale`: `library` (default) opens the library's calendar in a sheet; `native` opens the platform's own picker. */
81
+ picker?: "library" | "native";
82
+ /** With `locale`: the words the field says; replace any of them to translate. */
83
+ strings?: Partial<DateFieldStrings>;
84
+ };
85
+
86
+ let {
87
+ id,
88
+ value = $bindable<Exclude<Props["value"], undefined>>(),
89
+ name = "",
90
+ label = "",
91
+ hideLabel = false,
92
+ hint = "",
93
+ error = "",
94
+ min,
95
+ max,
96
+ step,
97
+ required = false,
98
+ disabled = false,
99
+ readonly = false,
100
+ size = "md",
101
+ class: className = "",
102
+ locale,
103
+ format,
104
+ placeholder,
105
+ picker = "library",
106
+ strings,
107
+ ...restProps
108
+ }: Props = $props();
109
+
110
+ // No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
111
+ // one that has a fallback (`props_invalid_value`), and a page that throws
112
+ // while it hydrates never becomes interactive. The default is applied
113
+ // here instead: at once, for the server and the first render, and again
114
+ // whenever a parent hands back `undefined`.
115
+ const applyDefaults = () => {
116
+ if (value === undefined) value = "";
117
+ };
118
+ applyDefaults();
119
+ $effect.pre(applyDefaults);
120
+ </script>
121
+
122
+ <TemporalField
123
+ kind="date"
124
+ {id}
125
+ bind:value
126
+ {name}
127
+ {label}
128
+ {hideLabel}
129
+ {hint}
130
+ {error}
131
+ {min}
132
+ {max}
133
+ {step}
134
+ {required}
135
+ {disabled}
136
+ {readonly}
137
+ {size}
138
+ class={className}
139
+ {locale}
140
+ {format}
141
+ {placeholder}
142
+ {picker}
143
+ {strings}
144
+ {...restProps}
145
+ />