zabi-components 8.0.0 → 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 (364) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +243 -10
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1192 -266
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +104 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  8. package/dist/atoms/Badge.svelte +36 -7
  9. package/dist/atoms/Badge.svelte.d.ts +2 -2
  10. package/dist/atoms/Button.svelte +177 -27
  11. package/dist/atoms/Button.svelte.d.ts +30 -2
  12. package/dist/atoms/Card.svelte +4 -4
  13. package/dist/atoms/CardHeader.svelte +16 -4
  14. package/dist/atoms/Checkbox.svelte +48 -21
  15. package/dist/atoms/Checkbox.svelte.d.ts +4 -2
  16. package/dist/atoms/CodeBlock.svelte +20 -2
  17. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  18. package/dist/atoms/ColorPicker.svelte +259 -49
  19. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  20. package/dist/atoms/Container.svelte +16 -8
  21. package/dist/atoms/DateField.svelte +107 -0
  22. package/dist/atoms/DateField.svelte.d.ts +53 -0
  23. package/dist/atoms/FieldMessages.svelte +69 -0
  24. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  25. package/dist/atoms/FloatingActionButton.svelte +174 -0
  26. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  27. package/dist/atoms/Heading.svelte +37 -5
  28. package/dist/atoms/Heading.svelte.d.ts +7 -0
  29. package/dist/atoms/IconButton.svelte +220 -24
  30. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  31. package/dist/atoms/Input.svelte +253 -52
  32. package/dist/atoms/Input.svelte.d.ts +32 -2
  33. package/dist/atoms/List.svelte +4 -1
  34. package/dist/atoms/ListItem.svelte +44 -11
  35. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  36. package/dist/atoms/Progress.svelte +27 -8
  37. package/dist/atoms/Progress.svelte.d.ts +4 -0
  38. package/dist/atoms/Radio.svelte +19 -3
  39. package/dist/atoms/Radio.svelte.d.ts +4 -2
  40. package/dist/atoms/Rating.svelte +494 -0
  41. package/dist/atoms/Rating.svelte.d.ts +56 -0
  42. package/dist/atoms/Select.svelte +433 -91
  43. package/dist/atoms/Select.svelte.d.ts +49 -3
  44. package/dist/atoms/SelectionControl.svelte +94 -17
  45. package/dist/atoms/SelectionControl.svelte.d.ts +4 -3
  46. package/dist/atoms/Skeleton.svelte +1 -1
  47. package/dist/atoms/Slider.svelte +309 -0
  48. package/dist/atoms/Slider.svelte.d.ts +40 -0
  49. package/dist/atoms/Spinner.svelte +57 -0
  50. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  51. package/dist/atoms/Table.svelte +151 -6
  52. package/dist/atoms/Table.svelte.d.ts +15 -3
  53. package/dist/atoms/TemporalField.svelte +140 -0
  54. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  55. package/dist/atoms/Text.svelte +23 -11
  56. package/dist/atoms/Text.svelte.d.ts +6 -3
  57. package/dist/atoms/Textarea.svelte +55 -44
  58. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  59. package/dist/atoms/ThemeToggle.svelte +223 -44
  60. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  61. package/dist/atoms/TimeField.svelte +108 -0
  62. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  63. package/dist/atoms/Toast.svelte +17 -4
  64. package/dist/atoms/Toast.svelte.d.ts +2 -0
  65. package/dist/atoms/Toggle.svelte +41 -8
  66. package/dist/atoms/Toggle.svelte.d.ts +11 -2
  67. package/dist/atoms/Tooltip.svelte +508 -23
  68. package/dist/atoms/Tooltip.svelte.d.ts +11 -0
  69. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  70. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  71. package/dist/atoms/index.d.ts +17 -0
  72. package/dist/atoms/index.js +12 -0
  73. package/dist/atoms/selection-control.styles.d.ts +36 -5
  74. package/dist/atoms/selection-control.styles.js +40 -9
  75. package/dist/components/atoms/index.d.ts +17 -0
  76. package/dist/components/atoms/index.d.ts.map +1 -1
  77. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  78. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  79. package/dist/components/index.d.ts +80 -2
  80. package/dist/components/index.d.ts.map +1 -1
  81. package/dist/components/molecules/index.d.ts +46 -0
  82. package/dist/components/molecules/index.d.ts.map +1 -1
  83. package/dist/components/molecules/toast-store.d.ts +53 -7
  84. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  85. package/dist/components/organisms/index.d.ts +5 -0
  86. package/dist/components/organisms/index.d.ts.map +1 -1
  87. package/dist/components/types/variants.d.ts +15 -1
  88. package/dist/components/types/variants.d.ts.map +1 -1
  89. package/dist/components/util/app-shell.d.ts +28 -0
  90. package/dist/components/util/app-shell.d.ts.map +1 -0
  91. package/dist/components/util/avatar.d.ts +36 -0
  92. package/dist/components/util/avatar.d.ts.map +1 -0
  93. package/dist/components/util/bottom-sheet.d.ts +30 -0
  94. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  95. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  96. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  97. package/dist/components/util/calendar.d.ts +56 -0
  98. package/dist/components/util/calendar.d.ts.map +1 -0
  99. package/dist/components/util/collapsible.d.ts +58 -0
  100. package/dist/components/util/collapsible.d.ts.map +1 -0
  101. package/dist/components/util/confirm-dialog.d.ts +12 -0
  102. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  103. package/dist/components/util/date.d.ts +96 -0
  104. package/dist/components/util/date.d.ts.map +1 -0
  105. package/dist/components/util/drawer.d.ts +13 -0
  106. package/dist/components/util/drawer.d.ts.map +1 -0
  107. package/dist/components/util/dropdown.d.ts +47 -0
  108. package/dist/components/util/dropdown.d.ts.map +1 -0
  109. package/dist/components/util/field.d.ts +39 -0
  110. package/dist/components/util/field.d.ts.map +1 -0
  111. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  112. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  113. package/dist/components/util/focus-utils.d.ts +75 -2
  114. package/dist/components/util/focus-utils.d.ts.map +1 -1
  115. package/dist/components/util/hydration.d.ts +45 -0
  116. package/dist/components/util/hydration.d.ts.map +1 -0
  117. package/dist/components/util/keyboard-inset.d.ts +18 -0
  118. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  119. package/dist/components/util/media-grid.d.ts +51 -0
  120. package/dist/components/util/media-grid.d.ts.map +1 -0
  121. package/dist/components/util/media.d.ts +6 -0
  122. package/dist/components/util/media.d.ts.map +1 -0
  123. package/dist/components/util/overlay-chrome.d.ts +53 -0
  124. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  125. package/dist/components/util/overlay.d.ts +72 -0
  126. package/dist/components/util/overlay.d.ts.map +1 -0
  127. package/dist/components/util/photo.d.ts +215 -0
  128. package/dist/components/util/photo.d.ts.map +1 -0
  129. package/dist/components/util/portal.d.ts +23 -0
  130. package/dist/components/util/portal.d.ts.map +1 -0
  131. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  132. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  133. package/dist/components/util/radio-keys.d.ts +25 -0
  134. package/dist/components/util/radio-keys.d.ts.map +1 -0
  135. package/dist/components/util/rating.d.ts +23 -0
  136. package/dist/components/util/rating.d.ts.map +1 -0
  137. package/dist/components/util/ready-made-strings.d.ts +77 -0
  138. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  139. package/dist/components/util/scroll-padding.d.ts +20 -0
  140. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  141. package/dist/components/util/segmented-control.d.ts +16 -0
  142. package/dist/components/util/segmented-control.d.ts.map +1 -0
  143. package/dist/components/util/select.d.ts +41 -0
  144. package/dist/components/util/select.d.ts.map +1 -0
  145. package/dist/components/util/sheet-drag.d.ts +60 -0
  146. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  147. package/dist/components/util/sidebar.d.ts +63 -0
  148. package/dist/components/util/sidebar.d.ts.map +1 -0
  149. package/dist/components/util/sortable-list.d.ts +65 -0
  150. package/dist/components/util/sortable-list.d.ts.map +1 -0
  151. package/dist/components/util/stepper.d.ts +40 -0
  152. package/dist/components/util/stepper.d.ts.map +1 -0
  153. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  154. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  155. package/dist/components/util/theme-mode.d.ts +80 -0
  156. package/dist/components/util/theme-mode.d.ts.map +1 -0
  157. package/dist/components/util/toaster.d.ts +93 -0
  158. package/dist/components/util/toaster.d.ts.map +1 -0
  159. package/dist/components/util/tooltip.d.ts +10 -0
  160. package/dist/components/util/tooltip.d.ts.map +1 -0
  161. package/dist/components/util/top-navbar.d.ts +11 -0
  162. package/dist/components/util/top-navbar.d.ts.map +1 -0
  163. package/dist/components/util/touch-target.d.ts +31 -0
  164. package/dist/components/util/touch-target.d.ts.map +1 -0
  165. package/dist/components/util/zabi-strings.d.ts +133 -0
  166. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  167. package/dist/create-theme/cli.js +169 -0
  168. package/dist/create-theme/index.d.ts +176 -0
  169. package/dist/create-theme/index.js +817 -0
  170. package/dist/create-theme/lib/ramp-math.js +128 -0
  171. package/dist/create-theme/lib/resolve.js +141 -0
  172. package/dist/create-theme/theme-data.js +1116 -0
  173. package/dist/index.d.ts +80 -2
  174. package/dist/index.js +57 -2
  175. package/dist/molecules/Alert.svelte +33 -5
  176. package/dist/molecules/Alert.svelte.d.ts +2 -0
  177. package/dist/molecules/AppBar.svelte +415 -0
  178. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  179. package/dist/molecules/AvatarGroup.svelte +116 -0
  180. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  181. package/dist/molecules/BottomSheet.svelte +540 -0
  182. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  183. package/dist/molecules/BottomTabBar.svelte +297 -0
  184. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  185. package/dist/molecules/Calendar.svelte +597 -0
  186. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  187. package/dist/molecules/Collapsible.svelte +243 -0
  188. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  189. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  190. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  191. package/dist/molecules/ComponentDemo.svelte +16 -4
  192. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  193. package/dist/molecules/ConfirmDialog.svelte +253 -0
  194. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  195. package/dist/molecules/ContactForm.svelte +34 -17
  196. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  197. package/dist/molecules/Drawer.svelte +394 -0
  198. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  199. package/dist/molecules/Dropdown.svelte +509 -48
  200. package/dist/molecules/Dropdown.svelte.d.ts +45 -6
  201. package/dist/molecules/DropdownItem.svelte +122 -0
  202. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  203. package/dist/molecules/EmptyState.svelte +55 -10
  204. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  205. package/dist/molecules/FormField.svelte +9 -1
  206. package/dist/molecules/FormField.svelte.d.ts +2 -0
  207. package/dist/molecules/Header.svelte +1 -1
  208. package/dist/molecules/ImageUpload.svelte +383 -70
  209. package/dist/molecules/ImageUpload.svelte.d.ts +47 -2
  210. package/dist/molecules/MediaGrid.svelte +527 -0
  211. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  212. package/dist/molecules/Modal.svelte +288 -72
  213. package/dist/molecules/Modal.svelte.d.ts +51 -2
  214. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  215. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  216. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  217. package/dist/molecules/NavigationMenuList.svelte +10 -3
  218. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  219. package/dist/molecules/Page.svelte +32 -2
  220. package/dist/molecules/Page.svelte.d.ts +11 -0
  221. package/dist/molecules/PhotoGrid.svelte +433 -0
  222. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  223. package/dist/molecules/PhotoViewer.svelte +1116 -0
  224. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  225. package/dist/molecules/PropsTable.svelte +22 -10
  226. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  227. package/dist/molecules/PullToRefresh.svelte +263 -0
  228. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  229. package/dist/molecules/RadioGroup.svelte +52 -14
  230. package/dist/molecules/RadioGroup.svelte.d.ts +14 -3
  231. package/dist/molecules/Section.svelte +1 -1
  232. package/dist/molecules/SegmentedControl.svelte +323 -0
  233. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  234. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  235. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  236. package/dist/molecules/SidebarFooter.svelte +29 -6
  237. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  238. package/dist/molecules/SlideUp.svelte +220 -61
  239. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  240. package/dist/molecules/SortableList.svelte +632 -0
  241. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  242. package/dist/molecules/Stepper.svelte +548 -0
  243. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  244. package/dist/molecules/StickyActionBar.svelte +191 -0
  245. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  246. package/dist/molecules/SwipeableListItem.svelte +334 -0
  247. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  248. package/dist/molecules/Tabs.svelte +233 -9
  249. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  250. package/dist/molecules/Toaster.svelte +350 -5
  251. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  252. package/dist/molecules/ToasterToast.svelte +253 -67
  253. package/dist/molecules/ToasterToast.svelte.d.ts +9 -0
  254. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  255. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  256. package/dist/molecules/index.d.ts +46 -0
  257. package/dist/molecules/index.js +27 -0
  258. package/dist/molecules/toast-store.d.ts +53 -7
  259. package/dist/molecules/toast-store.js +21 -0
  260. package/dist/organisms/AppShell.svelte +196 -0
  261. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  262. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  263. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  264. package/dist/organisms/SidebarNavigation.svelte +96 -15
  265. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  266. package/dist/organisms/SidebarPanel.svelte +31 -10
  267. package/dist/organisms/SidebarShell.svelte +162 -11
  268. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  269. package/dist/organisms/TopNavbar.svelte +256 -31
  270. package/dist/organisms/TopNavbar.svelte.d.ts +26 -0
  271. package/dist/organisms/index.d.ts +5 -0
  272. package/dist/organisms/index.js +3 -0
  273. package/dist/types/components.d.ts +4 -0
  274. package/dist/types/components.d.ts.map +1 -1
  275. package/dist/types/index.d.ts +605 -25
  276. package/dist/types/index.d.ts.map +1 -1
  277. package/dist/types/variants.ts +17 -1
  278. package/dist/util/app-shell.d.ts +27 -0
  279. package/dist/util/app-shell.js +72 -0
  280. package/dist/util/avatar.d.ts +35 -0
  281. package/dist/util/avatar.js +52 -0
  282. package/dist/util/bottom-sheet.d.ts +29 -0
  283. package/dist/util/bottom-sheet.js +43 -0
  284. package/dist/util/bottom-tab-bar.d.ts +34 -0
  285. package/dist/util/bottom-tab-bar.js +54 -0
  286. package/dist/util/calendar.d.ts +55 -0
  287. package/dist/util/calendar.js +74 -0
  288. package/dist/util/collapsible.d.ts +57 -0
  289. package/dist/util/collapsible.js +23 -0
  290. package/dist/util/confirm-dialog.d.ts +11 -0
  291. package/dist/util/confirm-dialog.js +2 -0
  292. package/dist/util/date.d.ts +95 -0
  293. package/dist/util/date.js +237 -0
  294. package/dist/util/drawer.d.ts +12 -0
  295. package/dist/util/drawer.js +7 -0
  296. package/dist/util/dropdown.d.ts +46 -0
  297. package/dist/util/dropdown.js +21 -0
  298. package/dist/util/field.d.ts +38 -0
  299. package/dist/util/field.js +38 -0
  300. package/dist/util/fit-in-viewport.d.ts +200 -0
  301. package/dist/util/fit-in-viewport.js +399 -0
  302. package/dist/util/focus-utils.d.ts +75 -2
  303. package/dist/util/focus-utils.js +182 -9
  304. package/dist/util/hydration.d.ts +44 -0
  305. package/dist/util/hydration.js +58 -0
  306. package/dist/util/keyboard-inset.d.ts +17 -0
  307. package/dist/util/keyboard-inset.js +53 -0
  308. package/dist/util/media-grid.d.ts +50 -0
  309. package/dist/util/media-grid.js +81 -0
  310. package/dist/util/media.d.ts +5 -0
  311. package/dist/util/media.js +10 -0
  312. package/dist/util/overlay-chrome.d.ts +52 -0
  313. package/dist/util/overlay-chrome.js +56 -0
  314. package/dist/util/overlay.d.ts +71 -0
  315. package/dist/util/overlay.js +240 -0
  316. package/dist/util/photo.d.ts +214 -0
  317. package/dist/util/photo.js +206 -0
  318. package/dist/util/portal.d.ts +22 -0
  319. package/dist/util/portal.js +50 -0
  320. package/dist/util/pull-to-refresh.d.ts +26 -0
  321. package/dist/util/pull-to-refresh.js +30 -0
  322. package/dist/util/radio-keys.d.ts +24 -0
  323. package/dist/util/radio-keys.js +49 -0
  324. package/dist/util/rating.d.ts +22 -0
  325. package/dist/util/rating.js +26 -0
  326. package/dist/util/ready-made-strings.d.ts +76 -0
  327. package/dist/util/ready-made-strings.js +67 -0
  328. package/dist/util/scroll-padding.d.ts +19 -0
  329. package/dist/util/scroll-padding.js +44 -0
  330. package/dist/util/segmented-control.d.ts +15 -0
  331. package/dist/util/segmented-control.js +1 -0
  332. package/dist/util/select.d.ts +40 -0
  333. package/dist/util/select.js +63 -0
  334. package/dist/util/sheet-drag.d.ts +59 -0
  335. package/dist/util/sheet-drag.js +241 -0
  336. package/dist/util/sidebar.d.ts +62 -0
  337. package/dist/util/sidebar.js +38 -0
  338. package/dist/util/sortable-list.d.ts +64 -0
  339. package/dist/util/sortable-list.js +53 -0
  340. package/dist/util/stepper.d.ts +39 -0
  341. package/dist/util/stepper.js +38 -0
  342. package/dist/util/swipeable-list-item.d.ts +35 -0
  343. package/dist/util/swipeable-list-item.js +32 -0
  344. package/dist/util/theme-mode.d.ts +79 -0
  345. package/dist/util/theme-mode.js +121 -0
  346. package/dist/util/toaster.d.ts +92 -0
  347. package/dist/util/toaster.js +75 -0
  348. package/dist/util/tooltip.d.ts +9 -0
  349. package/dist/util/tooltip.js +18 -0
  350. package/dist/util/top-navbar.d.ts +10 -0
  351. package/dist/util/top-navbar.js +5 -0
  352. package/dist/util/touch-target.d.ts +30 -0
  353. package/dist/util/touch-target.js +30 -0
  354. package/dist/util/zabi-strings.d.ts +132 -0
  355. package/dist/util/zabi-strings.js +105 -0
  356. package/dist/zabi-components-colors.css +5 -2
  357. package/dist/zabi-components-theme-dark-only.css +543 -241
  358. package/dist/zabi-components-theme-dark.css +543 -241
  359. package/dist/zabi-components-theme-only.css +1584 -367
  360. package/dist/zabi-components-theme.css +1584 -367
  361. package/dist/zabi-components.css +3885 -518
  362. package/docs/lucide-icons.md +40 -17
  363. package/docs/theme-imports.md +81 -11
  364. package/package.json +35 -8
@@ -1,8 +1,13 @@
1
1
  <script lang="ts">
2
+ import type { HTMLInputAttributes } from "svelte/elements";
2
3
  import SelectionControl from "./SelectionControl.svelte";
3
4
  import type { SelectionControlMarkProps } from "./SelectionControl.svelte";
4
5
 
5
- interface Props {
6
+ /** Other attributes (`required`, `data-*`, `aria-*`, ...) land on the `<input>`. */
7
+ type Props = Omit<
8
+ HTMLInputAttributes,
9
+ "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"
10
+ > & {
6
11
  /** Extra classes for the host element. */
7
12
  class?: string;
8
13
  id?: string;
@@ -15,7 +20,7 @@
15
20
  checked?: boolean;
16
21
  onChange?: (event: Event) => void;
17
22
  onchange?: (event: Event) => void;
18
- }
23
+ };
19
24
 
20
25
  let {
21
26
  class: className = "",
@@ -26,32 +31,54 @@
26
31
  loading = false,
27
32
  label = "",
28
33
  defaultChecked = false,
29
- checked = $bindable(defaultChecked),
34
+ checked = $bindable<Exclude<Props["checked"], undefined>>(),
30
35
  onChange,
31
36
  onchange,
32
37
  ...restProps
33
38
  }: Props = $props();
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 (checked === undefined) checked = defaultChecked;
47
+ };
48
+ applyDefaults();
49
+ $effect.pre(applyDefaults);
34
50
  </script>
35
51
 
36
52
  {#snippet mark(props: SelectionControlMarkProps)}
37
- <span
38
- class="pointer-events-none absolute inline-block size-3 shrink-0 animate-spin rounded-full border-2 border-brand-500 border-t-transparent z-10 opacity-0 group-has-[aria-busy=true]/control:opacity-100 group-has-checked/control:border-base-50"
39
- aria-hidden="true"
40
- ></span>
41
- <svg
42
- class="absolute w-3 h-3 text-base-50 pointer-events-none z-10 opacity-0 group-has-checked/control:opacity-100"
43
- fill="none"
44
- stroke="currentColor"
45
- viewBox="0 0 24 24"
46
- aria-hidden="true"
47
- >
48
- <path
49
- stroke-linecap="round"
50
- stroke-linejoin="round"
51
- stroke-width="2.5"
52
- d="M5 13l4 4L19 7"
53
- />
54
- </svg>
53
+ <!-- While loading the ring stands in for the tick, so the two never overlap;
54
+ the fill still says whether the box is checked. The ring is only in the
55
+ DOM while busy, which is what lets it take Spinner's reduced-motion
56
+ pulse: a pulse animates opacity and would un-hide a ring that was
57
+ merely transparent. It is drawn in the text colour so the gap in it
58
+ survives: a hand-written border colour class repaints all four sides. -->
59
+ {#if props.loading}
60
+ <span
61
+ class="pointer-events-none absolute z-10 inline-block size-3 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent motion-reduce:animate-pulse {props.checked
62
+ ? 'text-action-primary'
63
+ : 'text-brand-500'}"
64
+ aria-hidden="true"
65
+ ></span>
66
+ {:else}
67
+ <svg
68
+ class="absolute w-3 h-3 text-action-primary pointer-events-none z-10 opacity-0 group-has-checked/control:opacity-100"
69
+ fill="none"
70
+ stroke="currentColor"
71
+ viewBox="0 0 24 24"
72
+ aria-hidden="true"
73
+ >
74
+ <path
75
+ stroke-linecap="round"
76
+ stroke-linejoin="round"
77
+ stroke-width="2.5"
78
+ d="M5 13l4 4L19 7"
79
+ />
80
+ </svg>
81
+ {/if}
55
82
  {/snippet}
56
83
 
57
84
  <SelectionControl
@@ -1,4 +1,6 @@
1
- interface Props {
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ /** Other attributes (`required`, `data-*`, `aria-*`, ...) land on the `<input>`. */
3
+ type Props = Omit<HTMLInputAttributes, "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"> & {
2
4
  /** Extra classes for the host element. */
3
5
  class?: string;
4
6
  id?: string;
@@ -11,7 +13,7 @@ interface Props {
11
13
  checked?: boolean;
12
14
  onChange?: (event: Event) => void;
13
15
  onchange?: (event: Event) => void;
14
- }
16
+ };
15
17
  declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
16
18
  type Checkbox = ReturnType<typeof Checkbox>;
17
19
  export default Checkbox;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { DEFAULT_CODE_BLOCK_TEXTS, zabiStringsFor } from "../util/zabi-strings.js";
2
3
  import Check from "@lucide/svelte/icons/check";
3
4
  import Copy from "@lucide/svelte/icons/copy";
4
5
  import IconButton from "./IconButton.svelte";
@@ -12,6 +13,10 @@
12
13
  /** @deprecated use `class`. */
13
14
  className?: string;
14
15
  showCopyButton?: boolean;
16
+ /** Accessible name of the copy button. */
17
+ copyLabel?: string;
18
+ /** Its name for the two seconds after the code was copied. */
19
+ copiedLabel?: string;
15
20
  /** If true, `{@html code}` — only trusted, sanitized input. */
16
21
  trustHtml?: boolean;
17
22
  }
@@ -22,10 +27,17 @@
22
27
  class: classAttr = "",
23
28
  className: legacyClass = "",
24
29
  showCopyButton = true,
30
+ copyLabel: copyLabelGiven,
31
+ copiedLabel: copiedLabelGiven,
25
32
  trustHtml = false,
26
33
  ...restProps
27
34
  }: Props & Record<string, unknown> = $props();
28
35
 
36
+ /** Each text: the prop, else the app-wide word from a `ZabiStringsProvider` above this one, else English. */
37
+ const provided = zabiStringsFor("codeBlock");
38
+ const copyLabel = $derived(copyLabelGiven ?? provided()?.copyLabel ?? DEFAULT_CODE_BLOCK_TEXTS.copyLabel);
39
+ const copiedLabel = $derived(copiedLabelGiven ?? provided()?.copiedLabel ?? DEFAULT_CODE_BLOCK_TEXTS.copiedLabel);
40
+
29
41
  /** `class` is the public prop; `className` is a deprecated alias.
30
42
  * Both are merged here so existing call sites keep working. */
31
43
  const className = $derived(cn(`${classAttr} ${legacyClass}`));
@@ -61,7 +73,7 @@
61
73
  <IconButton
62
74
  variant="ghost"
63
75
  size="sm"
64
- label={copied ? "Code copied to clipboard" : "Copy code to clipboard"}
76
+ label={copied ? copiedLabel : copyLabel}
65
77
  onclick={copyToClipboard}
66
78
  >
67
79
  {#if copied}
@@ -70,6 +82,9 @@
70
82
  <Copy size={16} class="shrink-0" aria-hidden="true" />
71
83
  {/if}
72
84
  </IconButton>
85
+ <!-- A button's new name is not read out while it keeps focus.
86
+ This says it: always in the page, filled when the code is copied. -->
87
+ <span class="sr-only" role="status" data-code-block-status>{copied ? copiedLabel : ""}</span>
73
88
  {/if}
74
89
  </div>
75
90
 
@@ -80,8 +95,11 @@
80
95
  </div>
81
96
 
82
97
  <style>
98
+ /* The token's default is this same stack. It is repeated as the fallback
99
+ because Tailwind only emits theme variables it sees used, and it does
100
+ not read a component's scoped styles. */
83
101
  .code-block {
84
- font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
102
+ font-family: var(--font-family-mono, "Monaco", "Menlo", "Ubuntu Mono", monospace);
85
103
  }
86
104
 
87
105
  :global(.language-svelte .token.tag),
@@ -6,6 +6,10 @@ interface Props {
6
6
  /** @deprecated use `class`. */
7
7
  className?: string;
8
8
  showCopyButton?: boolean;
9
+ /** Accessible name of the copy button. */
10
+ copyLabel?: string;
11
+ /** Its name for the two seconds after the code was copied. */
12
+ copiedLabel?: string;
9
13
  /** If true, `{@html code}` — only trusted, sanitized input. */
10
14
  trustHtml?: boolean;
11
15
  }
@@ -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,100 @@
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
+ aria-label={text.area}
492
+ onpointerdown={handleMapPointerDown}
493
+ onpointermove={handleMapPointerMove}
494
+ onpointerup={handleMapPointerUp}
495
+ onpointercancel={handleMapPointerUp}
319
496
  >
320
497
  <canvas
321
498
  use:setColorMap
322
499
  width={256}
323
500
  height={192}
324
- class="w-full h-full"
501
+ class="w-full h-full rounded-container"
325
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. -->
326
532
  <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"
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"
328
534
  style="left: {pickerIndicatorX}; top: {pickerIndicatorY};"
329
535
  ></div>
330
536
  </div>
331
537
 
332
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. -->
333
543
  <div
334
- class="relative h-6 rounded-control overflow-hidden"
544
+ class="relative h-6 pointer-coarse:h-11 rounded-control {RING_WITHIN}"
335
545
  >
336
546
  <div
337
- class="absolute inset-0"
547
+ class="absolute inset-0 rounded-control"
338
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%));"
339
549
  ></div>
340
550
  <input
@@ -344,7 +554,7 @@
344
554
  value={hue}
345
555
  oninput={handleHueChange}
346
556
  class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
347
- aria-label="Hue slider"
557
+ aria-label={text.hue}
348
558
  />
349
559
  <div
350
560
  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">;