zabi-components 7.0.2 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (433) hide show
  1. package/CHANGELOG.md +1924 -0
  2. package/README.md +287 -11
  3. package/THEME.md +149 -332
  4. package/THEMING.md +1329 -163
  5. package/dist/atoms/ActionPanel.svelte +24 -9
  6. package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
  7. package/dist/atoms/Avatar.svelte +104 -0
  8. package/dist/atoms/Avatar.svelte.d.ts +34 -0
  9. package/dist/atoms/Badge.svelte +124 -58
  10. package/dist/atoms/Badge.svelte.d.ts +17 -5
  11. package/dist/atoms/Button.svelte +212 -62
  12. package/dist/atoms/Button.svelte.d.ts +33 -2
  13. package/dist/atoms/Card.svelte +31 -28
  14. package/dist/atoms/Card.svelte.d.ts +5 -2
  15. package/dist/atoms/CardContent.svelte +10 -2
  16. package/dist/atoms/CardContent.svelte.d.ts +2 -0
  17. package/dist/atoms/CardFooter.svelte +10 -2
  18. package/dist/atoms/CardFooter.svelte.d.ts +2 -0
  19. package/dist/atoms/CardHeader.svelte +26 -6
  20. package/dist/atoms/CardHeader.svelte.d.ts +2 -0
  21. package/dist/atoms/Checkbox.svelte +52 -21
  22. package/dist/atoms/Checkbox.svelte.d.ts +6 -2
  23. package/dist/atoms/CodeBlock.svelte +35 -9
  24. package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
  25. package/dist/atoms/ColorPicker.svelte +272 -52
  26. package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
  27. package/dist/atoms/Container.svelte +17 -10
  28. package/dist/atoms/DateField.svelte +107 -0
  29. package/dist/atoms/DateField.svelte.d.ts +53 -0
  30. package/dist/atoms/Divider.svelte +4 -4
  31. package/dist/atoms/FeatureCard.svelte +4 -3
  32. package/dist/atoms/FieldMessages.svelte +69 -0
  33. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  34. package/dist/atoms/FloatingActionButton.svelte +174 -0
  35. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  36. package/dist/atoms/Heading.svelte +93 -32
  37. package/dist/atoms/Heading.svelte.d.ts +24 -4
  38. package/dist/atoms/IconButton.svelte +242 -53
  39. package/dist/atoms/IconButton.svelte.d.ts +49 -4
  40. package/dist/atoms/Input.svelte +267 -74
  41. package/dist/atoms/Input.svelte.d.ts +32 -2
  42. package/dist/atoms/List.svelte +13 -2
  43. package/dist/atoms/List.svelte.d.ts +2 -0
  44. package/dist/atoms/ListItem.svelte +44 -11
  45. package/dist/atoms/ListItem.svelte.d.ts +5 -0
  46. package/dist/atoms/ListItemLeading.svelte +4 -2
  47. package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
  48. package/dist/atoms/OptimizedImage.svelte +11 -2
  49. package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
  50. package/dist/atoms/Progress.svelte +31 -9
  51. package/dist/atoms/Progress.svelte.d.ts +6 -0
  52. package/dist/atoms/Radio.svelte +23 -3
  53. package/dist/atoms/Radio.svelte.d.ts +6 -2
  54. package/dist/atoms/Rating.svelte +494 -0
  55. package/dist/atoms/Rating.svelte.d.ts +56 -0
  56. package/dist/atoms/Select.svelte +475 -135
  57. package/dist/atoms/Select.svelte.d.ts +52 -2
  58. package/dist/atoms/SelectionControl.svelte +99 -18
  59. package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
  60. package/dist/atoms/Skeleton.svelte +5 -3
  61. package/dist/atoms/Slider.svelte +309 -0
  62. package/dist/atoms/Slider.svelte.d.ts +40 -0
  63. package/dist/atoms/Spinner.svelte +57 -0
  64. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  65. package/dist/atoms/Table.svelte +152 -6
  66. package/dist/atoms/Table.svelte.d.ts +15 -3
  67. package/dist/atoms/TemporalField.svelte +140 -0
  68. package/dist/atoms/TemporalField.svelte.d.ts +32 -0
  69. package/dist/atoms/Text.svelte +56 -15
  70. package/dist/atoms/Text.svelte.d.ts +13 -4
  71. package/dist/atoms/Textarea.svelte +57 -45
  72. package/dist/atoms/Textarea.svelte.d.ts +14 -2
  73. package/dist/atoms/ThemeToggle.svelte +238 -56
  74. package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
  75. package/dist/atoms/TimeField.svelte +108 -0
  76. package/dist/atoms/TimeField.svelte.d.ts +54 -0
  77. package/dist/atoms/Toast.svelte +25 -10
  78. package/dist/atoms/Toast.svelte.d.ts +2 -0
  79. package/dist/atoms/Toggle.svelte +60 -19
  80. package/dist/atoms/Toggle.svelte.d.ts +18 -2
  81. package/dist/atoms/Tooltip.svelte +583 -40
  82. package/dist/atoms/Tooltip.svelte.d.ts +26 -0
  83. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  84. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  85. package/dist/atoms/index.d.ts +17 -0
  86. package/dist/atoms/index.js +12 -0
  87. package/dist/atoms/selection-control.styles.d.ts +36 -5
  88. package/dist/atoms/selection-control.styles.js +40 -9
  89. package/dist/components/atoms/index.d.ts +17 -0
  90. package/dist/components/atoms/index.d.ts.map +1 -1
  91. package/dist/components/atoms/selection-control.styles.d.ts +36 -5
  92. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  93. package/dist/components/index.d.ts +83 -3
  94. package/dist/components/index.d.ts.map +1 -1
  95. package/dist/components/molecules/index.d.ts +46 -0
  96. package/dist/components/molecules/index.d.ts.map +1 -1
  97. package/dist/components/molecules/toast-store.d.ts +53 -7
  98. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  99. package/dist/components/organisms/index.d.ts +6 -0
  100. package/dist/components/organisms/index.d.ts.map +1 -1
  101. package/dist/components/types/variants.d.ts +15 -1
  102. package/dist/components/types/variants.d.ts.map +1 -1
  103. package/dist/components/util/app-shell.d.ts +28 -0
  104. package/dist/components/util/app-shell.d.ts.map +1 -0
  105. package/dist/components/util/avatar.d.ts +36 -0
  106. package/dist/components/util/avatar.d.ts.map +1 -0
  107. package/dist/components/util/bottom-sheet.d.ts +30 -0
  108. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  109. package/dist/components/util/bottom-tab-bar.d.ts +35 -0
  110. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  111. package/dist/components/util/calendar.d.ts +56 -0
  112. package/dist/components/util/calendar.d.ts.map +1 -0
  113. package/dist/components/util/cn.d.ts +8 -0
  114. package/dist/components/util/cn.d.ts.map +1 -0
  115. package/dist/components/util/collapsible.d.ts +58 -0
  116. package/dist/components/util/collapsible.d.ts.map +1 -0
  117. package/dist/components/util/confirm-dialog.d.ts +12 -0
  118. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  119. package/dist/components/util/date.d.ts +96 -0
  120. package/dist/components/util/date.d.ts.map +1 -0
  121. package/dist/components/util/drawer.d.ts +13 -0
  122. package/dist/components/util/drawer.d.ts.map +1 -0
  123. package/dist/components/util/dropdown.d.ts +47 -0
  124. package/dist/components/util/dropdown.d.ts.map +1 -0
  125. package/dist/components/util/field.d.ts +39 -0
  126. package/dist/components/util/field.d.ts.map +1 -0
  127. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  128. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  129. package/dist/components/util/focus-utils.d.ts +75 -2
  130. package/dist/components/util/focus-utils.d.ts.map +1 -1
  131. package/dist/components/util/hydration.d.ts +45 -0
  132. package/dist/components/util/hydration.d.ts.map +1 -0
  133. package/dist/components/util/keyboard-inset.d.ts +18 -0
  134. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  135. package/dist/components/util/media-grid.d.ts +51 -0
  136. package/dist/components/util/media-grid.d.ts.map +1 -0
  137. package/dist/components/util/media.d.ts +6 -0
  138. package/dist/components/util/media.d.ts.map +1 -0
  139. package/dist/components/util/overlay-chrome.d.ts +53 -0
  140. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  141. package/dist/components/util/overlay.d.ts +72 -0
  142. package/dist/components/util/overlay.d.ts.map +1 -0
  143. package/dist/components/util/photo.d.ts +215 -0
  144. package/dist/components/util/photo.d.ts.map +1 -0
  145. package/dist/components/util/portal.d.ts +23 -0
  146. package/dist/components/util/portal.d.ts.map +1 -0
  147. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  148. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  149. package/dist/components/util/radio-keys.d.ts +25 -0
  150. package/dist/components/util/radio-keys.d.ts.map +1 -0
  151. package/dist/components/util/rating.d.ts +23 -0
  152. package/dist/components/util/rating.d.ts.map +1 -0
  153. package/dist/components/util/ready-made-strings.d.ts +77 -0
  154. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  155. package/dist/components/util/scroll-padding.d.ts +20 -0
  156. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  157. package/dist/components/util/segmented-control.d.ts +16 -0
  158. package/dist/components/util/segmented-control.d.ts.map +1 -0
  159. package/dist/components/util/select.d.ts +41 -0
  160. package/dist/components/util/select.d.ts.map +1 -0
  161. package/dist/components/util/sheet-drag.d.ts +60 -0
  162. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  163. package/dist/components/util/sidebar.d.ts +63 -0
  164. package/dist/components/util/sidebar.d.ts.map +1 -0
  165. package/dist/components/util/sortable-list.d.ts +65 -0
  166. package/dist/components/util/sortable-list.d.ts.map +1 -0
  167. package/dist/components/util/ssr-safe.d.ts +4 -4
  168. package/dist/components/util/ssr-safe.d.ts.map +1 -1
  169. package/dist/components/util/stepper.d.ts +40 -0
  170. package/dist/components/util/stepper.d.ts.map +1 -0
  171. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  172. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  173. package/dist/components/util/theme-mode.d.ts +80 -0
  174. package/dist/components/util/theme-mode.d.ts.map +1 -0
  175. package/dist/components/util/toaster.d.ts +93 -0
  176. package/dist/components/util/toaster.d.ts.map +1 -0
  177. package/dist/components/util/tooltip.d.ts +10 -0
  178. package/dist/components/util/tooltip.d.ts.map +1 -0
  179. package/dist/components/util/top-navbar.d.ts +11 -0
  180. package/dist/components/util/top-navbar.d.ts.map +1 -0
  181. package/dist/components/util/touch-target.d.ts +31 -0
  182. package/dist/components/util/touch-target.d.ts.map +1 -0
  183. package/dist/components/util/zabi-strings.d.ts +133 -0
  184. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  185. package/dist/create-theme/cli.js +169 -0
  186. package/dist/create-theme/index.d.ts +176 -0
  187. package/dist/create-theme/index.js +817 -0
  188. package/dist/create-theme/lib/ramp-math.js +128 -0
  189. package/dist/create-theme/lib/resolve.js +141 -0
  190. package/dist/create-theme/theme-data.js +1116 -0
  191. package/dist/index.d.ts +83 -3
  192. package/dist/index.js +60 -8
  193. package/dist/molecules/Alert.svelte +73 -66
  194. package/dist/molecules/Alert.svelte.d.ts +9 -1
  195. package/dist/molecules/AppBar.svelte +415 -0
  196. package/dist/molecules/AppBar.svelte.d.ts +69 -0
  197. package/dist/molecules/AvatarGroup.svelte +116 -0
  198. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  199. package/dist/molecules/BottomSheet.svelte +540 -0
  200. package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
  201. package/dist/molecules/BottomTabBar.svelte +297 -0
  202. package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
  203. package/dist/molecules/Calendar.svelte +597 -0
  204. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  205. package/dist/molecules/Collapsible.svelte +243 -0
  206. package/dist/molecules/Collapsible.svelte.d.ts +44 -0
  207. package/dist/molecules/CollapsibleGroup.svelte +126 -0
  208. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  209. package/dist/molecules/ComponentDemo.svelte +25 -5
  210. package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
  211. package/dist/molecules/ConfirmDialog.svelte +253 -0
  212. package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
  213. package/dist/molecules/ContactForm.svelte +46 -19
  214. package/dist/molecules/ContactForm.svelte.d.ts +10 -0
  215. package/dist/molecules/Drawer.svelte +394 -0
  216. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  217. package/dist/molecules/Dropdown.svelte +540 -50
  218. package/dist/molecules/Dropdown.svelte.d.ts +49 -6
  219. package/dist/molecules/DropdownItem.svelte +122 -0
  220. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  221. package/dist/molecules/EmptyState.svelte +56 -10
  222. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  223. package/dist/molecules/Form.svelte +25 -14
  224. package/dist/molecules/Form.svelte.d.ts +13 -5
  225. package/dist/molecules/FormField.svelte +11 -2
  226. package/dist/molecules/FormField.svelte.d.ts +2 -0
  227. package/dist/molecules/Header.svelte +13 -3
  228. package/dist/molecules/Header.svelte.d.ts +2 -0
  229. package/dist/molecules/ImageUpload.svelte +422 -70
  230. package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
  231. package/dist/molecules/MediaGrid.svelte +527 -0
  232. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  233. package/dist/molecules/Modal.svelte +340 -45
  234. package/dist/molecules/Modal.svelte.d.ts +59 -7
  235. package/dist/molecules/NavigationMenu.svelte +15 -3
  236. package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
  237. package/dist/molecules/NavigationMenuContent.svelte +91 -36
  238. package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
  239. package/dist/molecules/NavigationMenuItem.svelte +10 -3
  240. package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
  241. package/dist/molecules/NavigationMenuLink.svelte +10 -2
  242. package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
  243. package/dist/molecules/NavigationMenuList.svelte +19 -4
  244. package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
  245. package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
  246. package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
  247. package/dist/molecules/Page.svelte +38 -2
  248. package/dist/molecules/Page.svelte.d.ts +13 -0
  249. package/dist/molecules/PhotoGrid.svelte +433 -0
  250. package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
  251. package/dist/molecules/PhotoViewer.svelte +1116 -0
  252. package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
  253. package/dist/molecules/PropsTable.svelte +26 -11
  254. package/dist/molecules/PropsTable.svelte.d.ts +5 -0
  255. package/dist/molecules/PullToRefresh.svelte +263 -0
  256. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  257. package/dist/molecules/RadioGroup.svelte +57 -15
  258. package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
  259. package/dist/molecules/Section.svelte +13 -5
  260. package/dist/molecules/Section.svelte.d.ts +3 -1
  261. package/dist/molecules/SegmentedControl.svelte +323 -0
  262. package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
  263. package/dist/molecules/SidebarBrandHeader.svelte +24 -5
  264. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
  265. package/dist/molecules/SidebarFooter.svelte +39 -8
  266. package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
  267. package/dist/molecules/SidebarNavSection.svelte +12 -4
  268. package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
  269. package/dist/molecules/SlideUp.svelte +253 -30
  270. package/dist/molecules/SlideUp.svelte.d.ts +31 -4
  271. package/dist/molecules/SortableList.svelte +632 -0
  272. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  273. package/dist/molecules/Stepper.svelte +548 -0
  274. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  275. package/dist/molecules/StickyActionBar.svelte +191 -0
  276. package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
  277. package/dist/molecules/SwipeableListItem.svelte +334 -0
  278. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  279. package/dist/molecules/Tabs.svelte +254 -24
  280. package/dist/molecules/Tabs.svelte.d.ts +15 -4
  281. package/dist/molecules/Toaster.svelte +352 -6
  282. package/dist/molecules/Toaster.svelte.d.ts +95 -2
  283. package/dist/molecules/ToasterToast.svelte +279 -62
  284. package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
  285. package/dist/molecules/UnsavedChangesBar.svelte +212 -0
  286. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  287. package/dist/molecules/index.d.ts +46 -0
  288. package/dist/molecules/index.js +27 -0
  289. package/dist/molecules/toast-store.d.ts +53 -7
  290. package/dist/molecules/toast-store.js +21 -0
  291. package/dist/organisms/AppShell.svelte +196 -0
  292. package/dist/organisms/AppShell.svelte.d.ts +61 -0
  293. package/dist/organisms/SidebarAccountPanel.svelte +111 -12
  294. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
  295. package/dist/organisms/SidebarNavigation.svelte +193 -69
  296. package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
  297. package/dist/organisms/SidebarPanel.svelte +45 -16
  298. package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
  299. package/dist/organisms/SidebarShell.svelte +263 -0
  300. package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
  301. package/dist/organisms/TopNavbar.svelte +329 -33
  302. package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
  303. package/dist/organisms/index.d.ts +6 -0
  304. package/dist/organisms/index.js +4 -0
  305. package/dist/routes/lib/variant-utils.d.ts +26 -0
  306. package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
  307. package/dist/types/components.d.ts +4 -0
  308. package/dist/types/components.d.ts.map +1 -1
  309. package/dist/types/events.js +1 -0
  310. package/dist/types/index.d.ts +605 -25
  311. package/dist/types/index.d.ts.map +1 -1
  312. package/dist/types/index.js +3 -0
  313. package/dist/types/variants.ts +17 -1
  314. package/dist/util/app-shell.d.ts +27 -0
  315. package/dist/util/app-shell.js +72 -0
  316. package/dist/util/avatar.d.ts +35 -0
  317. package/dist/util/avatar.js +52 -0
  318. package/dist/util/bottom-sheet.d.ts +29 -0
  319. package/dist/util/bottom-sheet.js +43 -0
  320. package/dist/util/bottom-tab-bar.d.ts +34 -0
  321. package/dist/util/bottom-tab-bar.js +54 -0
  322. package/dist/util/calendar.d.ts +55 -0
  323. package/dist/util/calendar.js +74 -0
  324. package/dist/util/cn.d.ts +7 -0
  325. package/dist/util/cn.js +48 -0
  326. package/dist/util/collapsible.d.ts +57 -0
  327. package/dist/util/collapsible.js +23 -0
  328. package/dist/util/confirm-dialog.d.ts +11 -0
  329. package/dist/util/confirm-dialog.js +2 -0
  330. package/dist/util/date.d.ts +95 -0
  331. package/dist/util/date.js +237 -0
  332. package/dist/util/drawer.d.ts +12 -0
  333. package/dist/util/drawer.js +7 -0
  334. package/dist/util/dropdown.d.ts +46 -0
  335. package/dist/util/dropdown.js +21 -0
  336. package/dist/util/field.d.ts +38 -0
  337. package/dist/util/field.js +38 -0
  338. package/dist/util/fit-in-viewport.d.ts +200 -0
  339. package/dist/util/fit-in-viewport.js +399 -0
  340. package/dist/util/focus-utils.d.ts +75 -2
  341. package/dist/util/focus-utils.js +182 -9
  342. package/dist/util/hydration.d.ts +44 -0
  343. package/dist/util/hydration.js +58 -0
  344. package/dist/util/keyboard-inset.d.ts +17 -0
  345. package/dist/util/keyboard-inset.js +53 -0
  346. package/dist/util/media-grid.d.ts +50 -0
  347. package/dist/util/media-grid.js +81 -0
  348. package/dist/util/media.d.ts +5 -0
  349. package/dist/util/media.js +10 -0
  350. package/dist/util/overlay-chrome.d.ts +52 -0
  351. package/dist/util/overlay-chrome.js +56 -0
  352. package/dist/util/overlay.d.ts +71 -0
  353. package/dist/util/overlay.js +240 -0
  354. package/dist/util/photo.d.ts +214 -0
  355. package/dist/util/photo.js +206 -0
  356. package/dist/util/portal.d.ts +22 -0
  357. package/dist/util/portal.js +50 -0
  358. package/dist/util/pull-to-refresh.d.ts +26 -0
  359. package/dist/util/pull-to-refresh.js +30 -0
  360. package/dist/util/radio-keys.d.ts +24 -0
  361. package/dist/util/radio-keys.js +49 -0
  362. package/dist/util/rating.d.ts +22 -0
  363. package/dist/util/rating.js +26 -0
  364. package/dist/util/ready-made-strings.d.ts +76 -0
  365. package/dist/util/ready-made-strings.js +67 -0
  366. package/dist/util/scroll-padding.d.ts +19 -0
  367. package/dist/util/scroll-padding.js +44 -0
  368. package/dist/util/segmented-control.d.ts +15 -0
  369. package/dist/util/segmented-control.js +1 -0
  370. package/dist/util/select.d.ts +40 -0
  371. package/dist/util/select.js +63 -0
  372. package/dist/util/sheet-drag.d.ts +59 -0
  373. package/dist/util/sheet-drag.js +241 -0
  374. package/dist/util/sidebar.d.ts +62 -0
  375. package/dist/util/sidebar.js +38 -0
  376. package/dist/util/sortable-list.d.ts +64 -0
  377. package/dist/util/sortable-list.js +53 -0
  378. package/dist/util/ssr-safe.d.ts +4 -4
  379. package/dist/util/stepper.d.ts +39 -0
  380. package/dist/util/stepper.js +38 -0
  381. package/dist/util/swipeable-list-item.d.ts +35 -0
  382. package/dist/util/swipeable-list-item.js +32 -0
  383. package/dist/util/theme-mode.d.ts +79 -0
  384. package/dist/util/theme-mode.js +121 -0
  385. package/dist/util/toaster.d.ts +92 -0
  386. package/dist/util/toaster.js +75 -0
  387. package/dist/util/tooltip.d.ts +9 -0
  388. package/dist/util/tooltip.js +18 -0
  389. package/dist/util/top-navbar.d.ts +10 -0
  390. package/dist/util/top-navbar.js +5 -0
  391. package/dist/util/touch-target.d.ts +30 -0
  392. package/dist/util/touch-target.js +30 -0
  393. package/dist/util/zabi-strings.d.ts +132 -0
  394. package/dist/util/zabi-strings.js +105 -0
  395. package/dist/zabi-components-colors.css +5 -2
  396. package/dist/zabi-components-theme-dark-only.css +574 -225
  397. package/dist/zabi-components-theme-dark.css +574 -225
  398. package/dist/zabi-components-theme-only.css +1680 -317
  399. package/dist/zabi-components-theme.css +1680 -317
  400. package/dist/zabi-components.css +4473 -1158
  401. package/docs/lucide-icons.md +41 -16
  402. package/docs/theme-imports.md +81 -11
  403. package/package.json +51 -29
  404. package/dist/lib/layout-width-tokens.d.ts +0 -17
  405. package/dist/lib/layout-width-tokens.d.ts.map +0 -1
  406. package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
  407. package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
  408. package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
  409. package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
  410. package/dist/lib/showcase/component-docs/List.d.ts +0 -3
  411. package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
  412. package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
  413. package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
  414. package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
  415. package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
  416. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
  417. package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
  418. package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
  419. package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
  420. package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
  421. package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
  422. package/dist/lib/showcase/component-docs/index.d.ts +0 -4
  423. package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
  424. package/dist/lib/showcase/components-catalog.d.ts +0 -3
  425. package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
  426. package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
  427. package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
  428. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
  429. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
  430. package/dist/routes/lib/focus-utils.d.ts +0 -12
  431. package/dist/routes/lib/focus-utils.ts +0 -110
  432. package/dist/routes/lib/ssr-safe.ts +0 -58
  433. package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
@@ -0,0 +1,191 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import { findScrollParent } from "../util/app-shell.js";
5
+ import { cn } from "../util/cn.js";
6
+ import { watchKeyboardInset } from "../util/keyboard-inset.js";
7
+ import { reserveScrollPaddingBottom } from "../util/scroll-padding.js";
8
+
9
+ /**
10
+ * Keeps a form's main button on screen: at the bottom of whatever
11
+ * scrolls, and above the on-screen keyboard when that is up.
12
+ *
13
+ * Put it after the form's last field. It is sticky, not fixed, so it
14
+ * keeps its place in the flow and the last field can always be scrolled
15
+ * clear of it. A form shorter than the screen ends where its fields end,
16
+ * and so would the bar: make the form a column as tall as the screen
17
+ * (`flex min-h-full flex-col`) and the bar takes the bottom of it.
18
+ *
19
+ * ```svelte
20
+ * <form class="flex min-h-full flex-col" onsubmit={save}>
21
+ * <div class="space-y-4 p-4">
22
+ * <Input label="Name" bind:value={name} />
23
+ * …
24
+ * </div>
25
+ * <StickyActionBar>
26
+ * <Button type="submit" size="lg" fullWidth>Save visit</Button>
27
+ * </StickyActionBar>
28
+ * </form>
29
+ * ```
30
+ *
31
+ * Where the keyboard covers the bottom of the page instead of shrinking
32
+ * it (iOS Safari, Chrome on Android), the bar rises above it. The covered
33
+ * part is read from `window.visualViewport`, and the bar is raised by as
34
+ * much of its own scrolling box as the keyboard covers: the keyboard's
35
+ * full height in a box that ends at the bottom of the screen, less in
36
+ * one that ends above a tab bar or inside a sheet. It never leaves its
37
+ * box. It takes a bottom margin of the same size, so the content gains
38
+ * that much room to scroll, which a short form needs to bring its last
39
+ * field above the keyboard. Where the page shrinks by itself, nothing is
40
+ * done.
41
+ *
42
+ * While it is mounted the bar also sets `scroll-padding-bottom` on its
43
+ * scrolling ancestor (or the page) to its own height plus that rise, so a
44
+ * field that takes focus is brought into view above the bar, not under
45
+ * it. Use one bar per scrolling box: two would both stick to the same
46
+ * edge, one on top of the other.
47
+ *
48
+ * In an `AppShell`, a screen with a form is a task of its own: leave the
49
+ * tab bar out (no `footer`) and let this bar be the bottom of the screen.
50
+ * With a tab bar it sits directly above it.
51
+ *
52
+ * `UnsavedChangesBar` is the other bar at the bottom of a form. That one
53
+ * appears only while there is something to save, announces itself, and
54
+ * brings its own Save and Discard. This one is always there, holds the
55
+ * buttons you give it, and follows the keyboard.
56
+ */
57
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "style"> & {
58
+ /**
59
+ * Accessible name. With it the bar is a group, so a screen reader
60
+ * says what the buttons belong to; without it, it is a plain box.
61
+ */
62
+ label?: string;
63
+ class?: string;
64
+ /** Added after the offset and margin the bar sets while the keyboard is up. */
65
+ style?: string;
66
+ /** The main action, and at most one or two beside it. */
67
+ children?: Snippet;
68
+ };
69
+
70
+ let { label, class: className = "", style = "", children, ...restProps }: Props = $props();
71
+
72
+ let host: HTMLDivElement | undefined = $state();
73
+ /** Height of the keyboard over the page, in px. 0 on the server and where the page shrinks by itself. */
74
+ let keyboardInset = $state(0);
75
+ /**
76
+ * How far the bar is raised from the bottom of its scrolling box, in px.
77
+ * Not the keyboard's height: only the part of this box the keyboard covers.
78
+ */
79
+ let lift = $state(0);
80
+
81
+ $effect(() =>
82
+ watchKeyboardInset((inset) => {
83
+ keyboardInset = inset;
84
+ }),
85
+ );
86
+
87
+ /**
88
+ * The keyboard covers the screen from the bottom of the visual viewport
89
+ * down. The bar has to rise by as much of its own scrolling box as lies
90
+ * under that line: all of the keyboard's height when the box ends at the
91
+ * bottom of the screen, less when it ends higher (above a tab bar, in a
92
+ * sheet), nothing when it ends above the keyboard. It never leaves the
93
+ * box: a box wholly under the keyboard keeps its bar at its own top.
94
+ */
95
+ $effect(() => {
96
+ const bar = host;
97
+ if (!bar || keyboardInset === 0) {
98
+ lift = 0;
99
+ return;
100
+ }
101
+ const container = findScrollParent(bar);
102
+ const measure = () => {
103
+ const viewport = window.visualViewport;
104
+ if (!viewport) return;
105
+ const keyboardTop = viewport.offsetTop + viewport.height;
106
+ // Where the bar sticks: the box's content edge, inside its
107
+ // border and its own bottom padding.
108
+ const padding = container
109
+ ? parseFloat(getComputedStyle(container).paddingBottom) || 0
110
+ : 0;
111
+ const boxBottom = container
112
+ ? container.getBoundingClientRect().top +
113
+ container.clientTop +
114
+ container.clientHeight -
115
+ padding
116
+ : document.documentElement.clientHeight;
117
+ const boxHeight =
118
+ (container ? container.clientHeight : document.documentElement.clientHeight) -
119
+ padding;
120
+ const room = Math.max(0, boxHeight - bar.getBoundingClientRect().height);
121
+ lift = Math.round(Math.min(Math.max(boxBottom - keyboardTop, 0), room));
122
+ };
123
+ measure();
124
+ // The box moves on the screen when the page scrolls or is resized.
125
+ // Captured, so a scroll of any ancestor is heard.
126
+ window.addEventListener("scroll", measure, { capture: true, passive: true });
127
+ window.addEventListener("resize", measure);
128
+ window.visualViewport?.addEventListener("resize", measure);
129
+ window.visualViewport?.addEventListener("scroll", measure);
130
+ // An overlay moves itself up over the keyboard, and its content with
131
+ // it: the box then ends above the keyboard, and the bar is not raised
132
+ // a second time. That move is heard here, whichever came first.
133
+ const resized =
134
+ container && typeof ResizeObserver === "function"
135
+ ? new ResizeObserver(measure)
136
+ : undefined;
137
+ if (container) resized?.observe(container);
138
+ return () => {
139
+ resized?.disconnect();
140
+ window.removeEventListener("scroll", measure, true);
141
+ window.removeEventListener("resize", measure);
142
+ window.visualViewport?.removeEventListener("resize", measure);
143
+ window.visualViewport?.removeEventListener("scroll", measure);
144
+ };
145
+ });
146
+
147
+ /**
148
+ * The browser scrolls a focused field into view, to the edge of the
149
+ * scroll container: under this bar, since it lies over that edge.
150
+ */
151
+ $effect(() => {
152
+ const bar = host;
153
+ if (!bar) return;
154
+ const raised = lift;
155
+ const container = findScrollParent(bar) ?? document.documentElement;
156
+ const reservation = reserveScrollPaddingBottom(container);
157
+ const reserve = () => {
158
+ reservation.set(Math.ceil(bar.getBoundingClientRect().height) + raised);
159
+ };
160
+ reserve();
161
+ const observer =
162
+ typeof ResizeObserver === "function" ? new ResizeObserver(reserve) : undefined;
163
+ observer?.observe(bar);
164
+ return () => {
165
+ observer?.disconnect();
166
+ reservation.release();
167
+ };
168
+ });
169
+
170
+ const keyboardOpen = $derived(keyboardInset > 0);
171
+ </script>
172
+
173
+ <!-- Nothing here takes focus by itself, and the bar never moves focus. -->
174
+ <div
175
+ bind:this={host}
176
+ role={label ? "group" : undefined}
177
+ aria-label={label}
178
+ data-keyboard-open={keyboardOpen ? "true" : undefined}
179
+ class={cn(
180
+ // `mt-auto`: in a column taller than its fields, the bar goes to the bottom.
181
+ "sticky bottom-0 z-sticky mt-auto flex flex-wrap items-center justify-end gap-[8px] border-t border-border-weak bg-surface-elevated pt-3",
182
+ "pl-[max(1rem,env(safe-area-inset-left))] pr-[max(1rem,env(safe-area-inset-right))]",
183
+ // The home indicator is under the keyboard while that is up.
184
+ keyboardOpen ? "pb-3" : "pb-[max(0.75rem,env(safe-area-inset-bottom))]",
185
+ className,
186
+ )}
187
+ style="{lift > 0 ? `bottom: ${lift}px; margin-bottom: ${lift}px;` : ''} {style}"
188
+ {...restProps}
189
+ >
190
+ {@render children?.()}
191
+ </div>
@@ -0,0 +1,65 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ /**
4
+ * Keeps a form's main button on screen: at the bottom of whatever
5
+ * scrolls, and above the on-screen keyboard when that is up.
6
+ *
7
+ * Put it after the form's last field. It is sticky, not fixed, so it
8
+ * keeps its place in the flow and the last field can always be scrolled
9
+ * clear of it. A form shorter than the screen ends where its fields end,
10
+ * and so would the bar: make the form a column as tall as the screen
11
+ * (`flex min-h-full flex-col`) and the bar takes the bottom of it.
12
+ *
13
+ * ```svelte
14
+ * <form class="flex min-h-full flex-col" onsubmit={save}>
15
+ * <div class="space-y-4 p-4">
16
+ * <Input label="Name" bind:value={name} />
17
+ * …
18
+ * </div>
19
+ * <StickyActionBar>
20
+ * <Button type="submit" size="lg" fullWidth>Save visit</Button>
21
+ * </StickyActionBar>
22
+ * </form>
23
+ * ```
24
+ *
25
+ * Where the keyboard covers the bottom of the page instead of shrinking
26
+ * it (iOS Safari, Chrome on Android), the bar rises above it. The covered
27
+ * part is read from `window.visualViewport`, and the bar is raised by as
28
+ * much of its own scrolling box as the keyboard covers: the keyboard's
29
+ * full height in a box that ends at the bottom of the screen, less in
30
+ * one that ends above a tab bar or inside a sheet. It never leaves its
31
+ * box. It takes a bottom margin of the same size, so the content gains
32
+ * that much room to scroll, which a short form needs to bring its last
33
+ * field above the keyboard. Where the page shrinks by itself, nothing is
34
+ * done.
35
+ *
36
+ * While it is mounted the bar also sets `scroll-padding-bottom` on its
37
+ * scrolling ancestor (or the page) to its own height plus that rise, so a
38
+ * field that takes focus is brought into view above the bar, not under
39
+ * it. Use one bar per scrolling box: two would both stick to the same
40
+ * edge, one on top of the other.
41
+ *
42
+ * In an `AppShell`, a screen with a form is a task of its own: leave the
43
+ * tab bar out (no `footer`) and let this bar be the bottom of the screen.
44
+ * With a tab bar it sits directly above it.
45
+ *
46
+ * `UnsavedChangesBar` is the other bar at the bottom of a form. That one
47
+ * appears only while there is something to save, announces itself, and
48
+ * brings its own Save and Discard. This one is always there, holds the
49
+ * buttons you give it, and follows the keyboard.
50
+ */
51
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "style"> & {
52
+ /**
53
+ * Accessible name. With it the bar is a group, so a screen reader
54
+ * says what the buttons belong to; without it, it is a plain box.
55
+ */
56
+ label?: string;
57
+ class?: string;
58
+ /** Added after the offset and margin the bar sets while the keyboard is up. */
59
+ style?: string;
60
+ /** The main action, and at most one or two beside it. */
61
+ children?: Snippet;
62
+ };
63
+ declare const StickyActionBar: import("svelte").Component<Props, {}, "">;
64
+ type StickyActionBar = ReturnType<typeof StickyActionBar>;
65
+ export default StickyActionBar;
@@ -0,0 +1,334 @@
1
+ <script lang="ts">
2
+ import { mergeStrings } from "../util/ready-made-strings.js";
3
+ import { zabiStringsFor } from "../util/zabi-strings.js";
4
+ import { tick, type Snippet } from "svelte";
5
+ import type { HTMLAttributes } from "svelte/elements";
6
+ import Ellipsis from "@lucide/svelte/icons/ellipsis";
7
+ import { cn } from "../util/cn.js";
8
+ import { isInsideToastRegion } from "../util/focus-utils.js";
9
+ import {
10
+ createVelocityTracker,
11
+ DRAG_SLOP,
12
+ FLICK_VELOCITY,
13
+ prefersReducedMotion,
14
+ timeOf,
15
+ } from "../util/sheet-drag.js";
16
+ import { generateId } from "../util/ssr-safe.js";
17
+ import {
18
+ DEFAULT_SWIPEABLE_LIST_ITEM_STRINGS,
19
+ registerOpenRow,
20
+ settleSwipe,
21
+ type SwipeableListItemAction,
22
+ type SwipeableListItemStrings,
23
+ } from "../util/swipeable-list-item.js";
24
+
25
+ /**
26
+ * A row with one or two actions behind its inline end, such as Delete on
27
+ * a draft. A finger swipes the row towards the inline start to show them;
28
+ * a swipe only shows them, and an action runs when its button is pressed.
29
+ *
30
+ * The swipe is a shortcut and never the only way. The row has a "more"
31
+ * button at its end that opens the same actions for a mouse, a keyboard,
32
+ * a switch or a screen reader, and they are ordinary buttons once open.
33
+ * Escape or a press elsewhere closes the row. In a list (`ul`, `ol`,
34
+ * `List`) one row is open at a time.
35
+ *
36
+ * It is the row's content and a group of buttons, not a list item: put
37
+ * it inside your own `li` or `ListItem`.
38
+ *
39
+ * ```svelte
40
+ * <ul>
41
+ * {#each drafts as draft (draft.id)}
42
+ * <li>
43
+ * <SwipeableListItem
44
+ * actions={[{ id: "delete", label: "Delete", tone: "danger", onselect: () => remove(draft) }]}
45
+ * >
46
+ * {draft.title}
47
+ * </SwipeableListItem>
48
+ * </li>
49
+ * {/each}
50
+ * </ul>
51
+ * ```
52
+ */
53
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "children"> & {
54
+ /** One or two actions, shown at the inline end. */
55
+ actions: SwipeableListItemAction[];
56
+ /** Whether the actions are shown. Bindable. */
57
+ open?: boolean;
58
+ /** Runs when the row opens or closes, by whatever means. */
59
+ onopenchange?: (open: boolean) => void;
60
+ /**
61
+ * The button at the end of the row that opens the actions without a
62
+ * swipe. Without it the swipe is the only way this component offers,
63
+ * and the app has to give another: the same actions on the page the
64
+ * row leads to, or in a menu (WCAG 2.5.1, 2.5.7).
65
+ */
66
+ showMoreButton?: boolean;
67
+ /** The words the row says by itself, for another language. */
68
+ strings?: Partial<SwipeableListItemStrings>;
69
+ class?: string;
70
+ /** The row's content. */
71
+ children?: Snippet;
72
+ };
73
+
74
+ let {
75
+ actions,
76
+ open = $bindable<Exclude<Props["open"], undefined>>(),
77
+ onopenchange,
78
+ showMoreButton = true,
79
+ strings,
80
+ class: className = "",
81
+ children,
82
+ ...restProps
83
+ }: Props = $props();
84
+
85
+ // No fallback on a bindable prop (see Collapsible): the default is applied here.
86
+ const applyDefaults = () => {
87
+ if (open === undefined) open = false;
88
+ };
89
+ applyDefaults();
90
+ $effect.pre(applyDefaults);
91
+
92
+ /** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
93
+ const provided = zabiStringsFor("swipeableListItem");
94
+ const text = $derived(mergeStrings(DEFAULT_SWIPEABLE_LIST_ITEM_STRINGS, provided(), strings));
95
+ const groupId = generateId("swipeable-actions");
96
+
97
+ let root = $state<HTMLDivElement>();
98
+ let track = $state<HTMLDivElement>();
99
+ let group = $state<HTMLDivElement>();
100
+ let moreButton = $state<HTMLButtonElement>();
101
+
102
+ /** While a finger drags the row: how many px of the actions show. */
103
+ let dragged = $state<number | null>(null);
104
+
105
+ function setOpen(next: boolean) {
106
+ if (open === next) return;
107
+ open = next;
108
+ onopenchange?.(next);
109
+ }
110
+
111
+ /** Closes the row and puts focus back on the button that opens it, when focus was in the actions. */
112
+ function close({ returnFocus = false } = {}) {
113
+ const held = !!group?.contains(document.activeElement);
114
+ setOpen(false);
115
+ if (returnFocus || held) moreButton?.focus();
116
+ }
117
+
118
+ async function toggleFromButton() {
119
+ if (open) {
120
+ close();
121
+ return;
122
+ }
123
+ setOpen(true);
124
+ // Opened on purpose, by a press: on to the first action.
125
+ await tick();
126
+ group?.querySelector<HTMLElement>("button")?.focus();
127
+ }
128
+
129
+ function select(action: SwipeableListItemAction) {
130
+ try {
131
+ action.onselect();
132
+ } finally {
133
+ close({ returnFocus: true });
134
+ }
135
+ }
136
+
137
+ function handleKeydown(event: KeyboardEvent) {
138
+ if (event.key !== "Escape" || !open || event.defaultPrevented) return;
139
+ event.preventDefault();
140
+ event.stopPropagation();
141
+ close({ returnFocus: true });
142
+ }
143
+
144
+ // One open row in a list, and a press anywhere else closes this one.
145
+ $effect(() => {
146
+ const element = root;
147
+ if (!open || !element) return;
148
+ const unregister = registerOpenRow(element, () => setOpen(false));
149
+ const onPointerDown = (event: PointerEvent) => {
150
+ const target = event.target;
151
+ if (target instanceof Node && element.contains(target)) return;
152
+ // A toast lies over the list; a press on it is for the toast.
153
+ if (isInsideToastRegion(target)) return;
154
+ setOpen(false);
155
+ };
156
+ document.addEventListener("pointerdown", onPointerDown, true);
157
+ return () => {
158
+ unregister();
159
+ document.removeEventListener("pointerdown", onPointerDown, true);
160
+ };
161
+ });
162
+
163
+ // --- The swipe. A finger or a stylus; a mouse has the button. The row
164
+ // has `touch-action: pan-y`, so a vertical move is the browser's (it
165
+ // scrolls and cancels the pointer) and only a sideways one comes here.
166
+ const tracker = createVelocityTracker();
167
+ let pointerId: number | null = null;
168
+ let startX = 0;
169
+ let startY = 0;
170
+ let startRevealed = 0;
171
+ let width = 0;
172
+ let swiping = false;
173
+ /** 1 where the inline start is on the left, -1 in a right-to-left page. */
174
+ let direction = 1;
175
+
176
+ const revealedAt = (clientX: number) =>
177
+ Math.max(0, Math.min(width, startRevealed + (startX - clientX) * direction));
178
+
179
+ function handlePointerDown(event: PointerEvent) {
180
+ if (pointerId !== null || event.pointerType === "mouse" || !group || !track) return;
181
+ pointerId = event.pointerId;
182
+ startX = event.clientX;
183
+ startY = event.clientY;
184
+ swiping = false;
185
+ width = group.offsetWidth;
186
+ startRevealed = open ? width : 0;
187
+ direction = getComputedStyle(track).direction === "rtl" ? -1 : 1;
188
+ }
189
+
190
+ function handlePointerMove(event: PointerEvent) {
191
+ if (event.pointerId !== pointerId) return;
192
+ const dx = event.clientX - startX;
193
+ const dy = event.clientY - startY;
194
+ if (!swiping) {
195
+ if (Math.abs(dx) < DRAG_SLOP && Math.abs(dy) < DRAG_SLOP) return;
196
+ // Clearly sideways, or it is not a swipe of the row.
197
+ if (Math.abs(dx) <= Math.abs(dy)) {
198
+ pointerId = null;
199
+ return;
200
+ }
201
+ swiping = true;
202
+ try {
203
+ track?.setPointerCapture(event.pointerId);
204
+ } catch {
205
+ /* a test DOM, or a pointer that is already gone */
206
+ }
207
+ // Measured from where the swipe was recognised, so the row does not jump by the slop.
208
+ startX = event.clientX;
209
+ tracker.reset(0, timeOf(event));
210
+ }
211
+ const revealed = revealedAt(event.clientX);
212
+ tracker.add(revealed, timeOf(event));
213
+ // Where motion is unwanted the row does not follow the finger; the actions appear at the end.
214
+ if (!prefersReducedMotion()) dragged = revealed;
215
+ }
216
+
217
+ function swallowClick(event: MouseEvent) {
218
+ // The click that ends a swipe is not a press of what is in the row.
219
+ event.preventDefault();
220
+ event.stopPropagation();
221
+ }
222
+
223
+ function finishPointer(event: PointerEvent, cancelled: boolean) {
224
+ if (event.pointerId !== pointerId) return;
225
+ pointerId = null;
226
+ const wasSwiping = swiping;
227
+ swiping = false;
228
+ dragged = null;
229
+ if (!wasSwiping || cancelled) return;
230
+ const revealed = revealedAt(event.clientX);
231
+ tracker.add(revealed, timeOf(event));
232
+ track?.addEventListener("click", swallowClick, { capture: true, once: true });
233
+ setTimeout(() => track?.removeEventListener("click", swallowClick, true), 0);
234
+ setOpen(settleSwipe(revealed, width, tracker.velocity(), FLICK_VELOCITY));
235
+ }
236
+
237
+ /** The actions are laid out and can be seen: open, or coming out under a finger. */
238
+ const shown = $derived(open || dragged !== null);
239
+
240
+ /** How far the row is moved, as CSS: by the width of the actions when open. */
241
+ const shift = $derived.by(() => {
242
+ if (dragged !== null) return `${dragged}px`;
243
+ return open ? "var(--swipeable-actions-width, 0px)" : "0px";
244
+ });
245
+
246
+ // The width of the actions, for the row to move by. Measured, since the labels are the app's.
247
+ $effect(() => {
248
+ const element = group;
249
+ const host = root;
250
+ if (!element || !host) return;
251
+ const measure = () => host.style.setProperty("--swipeable-actions-width", `${element.offsetWidth}px`);
252
+ measure();
253
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : undefined;
254
+ observer?.observe(element);
255
+ return () => observer?.disconnect();
256
+ });
257
+
258
+ const actionClasses = (tone: SwipeableListItemAction["tone"]) =>
259
+ cn(
260
+ // The ring is drawn inside the button: the row clips what reaches past its ends.
261
+ "flex min-h-11 min-w-[72px] cursor-pointer flex-col items-center justify-center gap-[4px] px-[12px] py-[8px] text-center text-xs font-medium transition-colors motion-reduce:transition-none focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-current forced-colors:border forced-colors:border-[ButtonText]",
262
+ tone === "danger"
263
+ ? "bg-action-danger text-action-danger-text hover:bg-action-danger-hover active:bg-action-danger-active"
264
+ : "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active",
265
+ );
266
+ </script>
267
+
268
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
269
+ <div
270
+ {...restProps}
271
+ bind:this={root}
272
+ class={cn("relative overflow-x-clip", className)}
273
+ data-swipeable-list-item
274
+ data-open={open ? "true" : "false"}
275
+ onkeydown={handleKeydown}
276
+ >
277
+ <!-- The row and its actions side by side, the actions past the end of the
278
+ row until the two are moved over. `pan-y`: a vertical move scrolls. -->
279
+ <div
280
+ bind:this={track}
281
+ class={cn(
282
+ // Towards the inline start: to the left, or to the right in a right-to-left page.
283
+ "flex touch-pan-y items-stretch [--swipeable-direction:-1] rtl:[--swipeable-direction:1]",
284
+ dragged === null && "transition-transform duration-200 ease-out motion-reduce:transition-none",
285
+ )}
286
+ style:transform="translateX(calc({shift} * var(--swipeable-direction, -1)))"
287
+ onpointerdown={handlePointerDown}
288
+ onpointermove={handlePointerMove}
289
+ onpointerup={(event) => finishPointer(event, false)}
290
+ onpointercancel={(event) => finishPointer(event, true)}
291
+ >
292
+ <div class="flex min-w-0 shrink-0 basis-full items-center gap-[8px]">
293
+ <div class="min-w-0 flex-1">
294
+ {@render children?.()}
295
+ </div>
296
+ {#if showMoreButton}
297
+ <button
298
+ bind:this={moreButton}
299
+ type="button"
300
+ class="flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-control text-description transition-colors hover:bg-surface-hover hover:text-headline active:bg-surface-active focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring motion-reduce:transition-none pointer-coarse:size-[44px]"
301
+ aria-label={text.actions}
302
+ aria-expanded={open}
303
+ aria-controls={groupId}
304
+ data-swipeable-more
305
+ onclick={toggleFromButton}
306
+ >
307
+ <Ellipsis size={20} aria-hidden="true" />
308
+ </button>
309
+ {/if}
310
+ </div>
311
+ <!-- In the page while closed, so the button above has something to
312
+ name; out of reach until the row is open. -->
313
+ <div
314
+ bind:this={group}
315
+ id={groupId}
316
+ role="group"
317
+ aria-label={text.actions}
318
+ class={cn("flex shrink-0 items-stretch", !shown && "invisible")}
319
+ style:max-inline-size="max(66%, {actions.length * 72}px)"
320
+ inert={!open}
321
+ data-swipeable-actions
322
+ >
323
+ {#each actions as action (action.id)}
324
+ <button type="button" class={actionClasses(action.tone)} onclick={() => select(action)}>
325
+ {#if action.icon}
326
+ {@const Icon = action.icon}
327
+ <span class="flex" aria-hidden="true"><Icon size={20} /></span>
328
+ {/if}
329
+ <span class="min-w-0 text-balance break-words">{action.label}</span>
330
+ </button>
331
+ {/each}
332
+ </div>
333
+ </div>
334
+ </div>
@@ -0,0 +1,54 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { type SwipeableListItemAction, type SwipeableListItemStrings } from "../util/swipeable-list-item.js";
4
+ /**
5
+ * A row with one or two actions behind its inline end, such as Delete on
6
+ * a draft. A finger swipes the row towards the inline start to show them;
7
+ * a swipe only shows them, and an action runs when its button is pressed.
8
+ *
9
+ * The swipe is a shortcut and never the only way. The row has a "more"
10
+ * button at its end that opens the same actions for a mouse, a keyboard,
11
+ * a switch or a screen reader, and they are ordinary buttons once open.
12
+ * Escape or a press elsewhere closes the row. In a list (`ul`, `ol`,
13
+ * `List`) one row is open at a time.
14
+ *
15
+ * It is the row's content and a group of buttons, not a list item: put
16
+ * it inside your own `li` or `ListItem`.
17
+ *
18
+ * ```svelte
19
+ * <ul>
20
+ * {#each drafts as draft (draft.id)}
21
+ * <li>
22
+ * <SwipeableListItem
23
+ * actions={[{ id: "delete", label: "Delete", tone: "danger", onselect: () => remove(draft) }]}
24
+ * >
25
+ * {draft.title}
26
+ * </SwipeableListItem>
27
+ * </li>
28
+ * {/each}
29
+ * </ul>
30
+ * ```
31
+ */
32
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "children"> & {
33
+ /** One or two actions, shown at the inline end. */
34
+ actions: SwipeableListItemAction[];
35
+ /** Whether the actions are shown. Bindable. */
36
+ open?: boolean;
37
+ /** Runs when the row opens or closes, by whatever means. */
38
+ onopenchange?: (open: boolean) => void;
39
+ /**
40
+ * The button at the end of the row that opens the actions without a
41
+ * swipe. Without it the swipe is the only way this component offers,
42
+ * and the app has to give another: the same actions on the page the
43
+ * row leads to, or in a menu (WCAG 2.5.1, 2.5.7).
44
+ */
45
+ showMoreButton?: boolean;
46
+ /** The words the row says by itself, for another language. */
47
+ strings?: Partial<SwipeableListItemStrings>;
48
+ class?: string;
49
+ /** The row's content. */
50
+ children?: Snippet;
51
+ };
52
+ declare const SwipeableListItem: import("svelte").Component<Props, {}, "open">;
53
+ type SwipeableListItem = ReturnType<typeof SwipeableListItem>;
54
+ export default SwipeableListItem;