zabi-components 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -0,0 +1,250 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import { findScrollParent, getAppShell } 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
+ import { resolveScrolledUnder, watchScrollEdge, type ScrollEdgeMode } from "../util/scroll-edge.js";
9
+
10
+ /**
11
+ * Keeps a form's main button on screen: at the bottom of whatever
12
+ * scrolls, and above the on-screen keyboard when that is up.
13
+ *
14
+ * Put it after the form's last field. It is sticky, not fixed, so it
15
+ * keeps its place in the flow and the last field can always be scrolled
16
+ * clear of it. A form shorter than the screen ends where its fields end,
17
+ * and so would the bar: make the form a column as tall as the screen
18
+ * (`flex min-h-full flex-col`) and the bar takes the bottom of it.
19
+ *
20
+ * ```svelte
21
+ * <form class="flex min-h-full flex-col" onsubmit={save}>
22
+ * <div class="space-y-4 p-4">
23
+ * <Input label="Name" bind:value={name} />
24
+ * …
25
+ * </div>
26
+ * <StickyActionBar>
27
+ * <Button type="submit" size="lg" fullWidth>Save visit</Button>
28
+ * </StickyActionBar>
29
+ * </form>
30
+ * ```
31
+ *
32
+ * Where the keyboard covers the bottom of the page instead of shrinking
33
+ * it (iOS Safari, Chrome on Android), the bar rises above it. The covered
34
+ * part is read from `window.visualViewport`, and the bar is raised by as
35
+ * much of its own scrolling box as the keyboard covers: the keyboard's
36
+ * full height in a box that ends at the bottom of the screen, less in
37
+ * one that ends above a tab bar or inside a sheet. It never leaves its
38
+ * box. It takes a bottom margin of the same size, so the content gains
39
+ * that much room to scroll, which a short form needs to bring its last
40
+ * field above the keyboard. Where the page shrinks by itself, nothing is
41
+ * done.
42
+ *
43
+ * While it is mounted the bar also sets `scroll-padding-bottom` on its
44
+ * scrolling ancestor (or the page) to its own height plus that rise, so a
45
+ * field that takes focus is brought into view above the bar, not under
46
+ * it. Use one bar per scrolling box: two would both stick to the same
47
+ * edge, one on top of the other.
48
+ *
49
+ * In an `AppShell`, a screen with a form is a task of its own: leave the
50
+ * tab bar out (no `footer`) and let this bar be the bottom of the screen.
51
+ * With a tab bar it sits directly above it: the shell's footer lies over
52
+ * the scroller, and the bar sticks to the footer's top edge, not under it.
53
+ *
54
+ * The bar is flush with the page and page-coloured while the form ends
55
+ * where the bar is; once content is under it, it turns to frosted glass
56
+ * with a hairline above it.
57
+ *
58
+ * `UnsavedChangesBar` is the other bar at the bottom of a form. That one
59
+ * appears only while there is something to save, announces itself, and
60
+ * brings its own Save and Discard. This one is always there, holds the
61
+ * buttons you give it, and follows the keyboard.
62
+ */
63
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "style"> & {
64
+ /**
65
+ * Accessible name. With it the bar is a group, so a screen reader
66
+ * says what the buttons belong to; without it, it is a plain box.
67
+ */
68
+ label?: string;
69
+ /**
70
+ * When the bar shows the glass and the hairline. `auto` follows the
71
+ * scroll position: flush at the end of the content, glass while
72
+ * content is under it. `always` is glass all the time, `never` flush
73
+ * all the time.
74
+ */
75
+ scrollEdge?: ScrollEdgeMode;
76
+ class?: string;
77
+ /** Added after the offset and margin the bar sets while the keyboard is up. */
78
+ style?: string;
79
+ /** The main action, and at most one or two beside it. */
80
+ children?: Snippet;
81
+ };
82
+
83
+ let {
84
+ label,
85
+ scrollEdge = "auto",
86
+ class: className = "",
87
+ style = "",
88
+ children,
89
+ ...restProps
90
+ }: Props = $props();
91
+
92
+ let host: HTMLDivElement | undefined = $state();
93
+
94
+ /**
95
+ * In a shell with a footer the footer lies over the bottom of the scroller
96
+ * by this much: the bar sticks above it, and the box it scrolls in
97
+ * extends under it.
98
+ */
99
+ const shell = getAppShell();
100
+ const underFooter = $derived(shell?.footerOverlays === true);
101
+ const footerHeight = $derived(shell?.footerHeight ?? 0);
102
+
103
+ /** Content is under the bar: from the shell if there is one, else from its own scroller. */
104
+ let ownScrolled = $state(false);
105
+ $effect(() => {
106
+ if (shell || scrollEdge !== "auto" || !host) return;
107
+ return watchScrollEdge(findScrollParent(host), (edge) => (ownScrolled = edge.bottom));
108
+ });
109
+ const scrolledUnder = $derived(
110
+ resolveScrolledUnder(scrollEdge, shell ? shell.scrolledBottom : ownScrolled),
111
+ );
112
+ /** Height of the keyboard over the page, in px. 0 on the server and where the page shrinks by itself. */
113
+ let keyboardInset = $state(0);
114
+ /**
115
+ * How far the bar is raised from the bottom of its scrolling box, in px.
116
+ * Not the keyboard's height: only the part of this box the keyboard covers.
117
+ */
118
+ let lift = $state(0);
119
+
120
+ $effect(() =>
121
+ watchKeyboardInset((inset) => {
122
+ keyboardInset = inset;
123
+ }),
124
+ );
125
+
126
+ /**
127
+ * The keyboard covers the screen from the bottom of the visual viewport
128
+ * down. The bar has to rise by as much of its own scrolling box as lies
129
+ * under that line: all of the keyboard's height when the box ends at the
130
+ * bottom of the screen, less when it ends higher (above a tab bar, in a
131
+ * sheet), nothing when it ends above the keyboard. It never leaves the
132
+ * box: a box wholly under the keyboard keeps its bar at its own top.
133
+ */
134
+ $effect(() => {
135
+ const bar = host;
136
+ if (!bar || keyboardInset === 0) {
137
+ lift = 0;
138
+ return;
139
+ }
140
+ const container = findScrollParent(bar);
141
+ const measure = () => {
142
+ const viewport = window.visualViewport;
143
+ if (!viewport) return;
144
+ const keyboardTop = viewport.offsetTop + viewport.height;
145
+ // Where the bar sticks: the box's content edge, inside its
146
+ // border and its own bottom padding.
147
+ const padding = container
148
+ ? parseFloat(getComputedStyle(container).paddingBottom) || 0
149
+ : 0;
150
+ const boxBottom = container
151
+ ? container.getBoundingClientRect().top +
152
+ container.clientTop +
153
+ container.clientHeight -
154
+ padding
155
+ : document.documentElement.clientHeight;
156
+ const boxHeight =
157
+ (container ? container.clientHeight : document.documentElement.clientHeight) -
158
+ padding;
159
+ const room = Math.max(0, boxHeight - bar.getBoundingClientRect().height);
160
+ lift = Math.round(Math.min(Math.max(boxBottom - keyboardTop, 0), room));
161
+ };
162
+ measure();
163
+ // The box moves on the screen when the page scrolls or is resized.
164
+ // Captured, so a scroll of any ancestor is heard.
165
+ window.addEventListener("scroll", measure, { capture: true, passive: true });
166
+ window.addEventListener("resize", measure);
167
+ window.visualViewport?.addEventListener("resize", measure);
168
+ window.visualViewport?.addEventListener("scroll", measure);
169
+ // An overlay moves itself up over the keyboard, and its content with
170
+ // it: the box then ends above the keyboard, and the bar is not raised
171
+ // a second time. That move is heard here, whichever came first.
172
+ const resized =
173
+ container && typeof ResizeObserver === "function"
174
+ ? new ResizeObserver(measure)
175
+ : undefined;
176
+ if (container) resized?.observe(container);
177
+ return () => {
178
+ resized?.disconnect();
179
+ window.removeEventListener("scroll", measure, true);
180
+ window.removeEventListener("resize", measure);
181
+ window.visualViewport?.removeEventListener("resize", measure);
182
+ window.visualViewport?.removeEventListener("scroll", measure);
183
+ };
184
+ });
185
+
186
+ /**
187
+ * The browser scrolls a focused field into view, to the edge of the
188
+ * scroll container: under this bar, since it lies over that edge.
189
+ */
190
+ $effect(() => {
191
+ const bar = host;
192
+ if (!bar) return;
193
+ // The bar's own offset from the bottom of the box: what the keyboard
194
+ // lifts it by, or the footer it sits above, whichever is more.
195
+ const raised = Math.max(lift, footerHeight);
196
+ const container = findScrollParent(bar) ?? document.documentElement;
197
+ const reservation = reserveScrollPaddingBottom(container);
198
+ const reserve = () => {
199
+ reservation.set(Math.ceil(bar.getBoundingClientRect().height) + raised);
200
+ };
201
+ reserve();
202
+ const observer =
203
+ typeof ResizeObserver === "function" ? new ResizeObserver(reserve) : undefined;
204
+ observer?.observe(bar);
205
+ return () => {
206
+ observer?.disconnect();
207
+ reservation.release();
208
+ };
209
+ });
210
+
211
+ const keyboardOpen = $derived(keyboardInset > 0);
212
+
213
+ /**
214
+ * Where it sticks. On its own the box ends at the bottom and the bar is
215
+ * lifted by `lift`. Over a footer it sticks above the footer, and the
216
+ * keyboard lifts it only as far as it reaches past the footer: the box
217
+ * extends under the footer, so `lift` counts from the box's bottom.
218
+ */
219
+ const offsetStyle = $derived.by(() => {
220
+ const overlay = "var(--app-shell-footer-overlay, 0px)";
221
+ if (!underFooter) return lift > 0 ? `bottom: ${lift}px; margin-bottom: ${lift}px;` : "";
222
+ if (lift > 0) {
223
+ return `bottom: max(${lift}px, ${overlay}); margin-bottom: max(0px, calc(${lift}px - ${overlay}));`;
224
+ }
225
+ return `bottom: ${overlay};`;
226
+ });
227
+ </script>
228
+
229
+ <!-- Nothing here takes focus by itself, and the bar never moves focus. -->
230
+ <div
231
+ bind:this={host}
232
+ role={label ? "group" : undefined}
233
+ aria-label={label}
234
+ data-keyboard-open={keyboardOpen ? "true" : undefined}
235
+ data-bar-edge="bottom"
236
+ data-scrolled-under={String(scrolledUnder)}
237
+ class={cn(
238
+ // `mt-auto`: in a column taller than its fields, the bar goes to the bottom.
239
+ "material-bar sticky bottom-0 z-sticky mt-auto flex flex-wrap items-center justify-end gap-[8px] pt-3",
240
+ "pl-[max(1rem,env(safe-area-inset-left))] pr-[max(1rem,env(safe-area-inset-right))]",
241
+ // The home indicator is under the keyboard while that is up, and under
242
+ // the footer, which already clears it, when there is one.
243
+ keyboardOpen || underFooter ? "pb-3" : "pb-[max(0.75rem,env(safe-area-inset-bottom))]",
244
+ className,
245
+ )}
246
+ style="{offsetStyle} {style}"
247
+ {...restProps}
248
+ >
249
+ {@render children?.()}
250
+ </div>
@@ -0,0 +1,78 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import { type ScrollEdgeMode } from "../util/scroll-edge.js";
4
+ /**
5
+ * Keeps a form's main button on screen: at the bottom of whatever
6
+ * scrolls, and above the on-screen keyboard when that is up.
7
+ *
8
+ * Put it after the form's last field. It is sticky, not fixed, so it
9
+ * keeps its place in the flow and the last field can always be scrolled
10
+ * clear of it. A form shorter than the screen ends where its fields end,
11
+ * and so would the bar: make the form a column as tall as the screen
12
+ * (`flex min-h-full flex-col`) and the bar takes the bottom of it.
13
+ *
14
+ * ```svelte
15
+ * <form class="flex min-h-full flex-col" onsubmit={save}>
16
+ * <div class="space-y-4 p-4">
17
+ * <Input label="Name" bind:value={name} />
18
+ * …
19
+ * </div>
20
+ * <StickyActionBar>
21
+ * <Button type="submit" size="lg" fullWidth>Save visit</Button>
22
+ * </StickyActionBar>
23
+ * </form>
24
+ * ```
25
+ *
26
+ * Where the keyboard covers the bottom of the page instead of shrinking
27
+ * it (iOS Safari, Chrome on Android), the bar rises above it. The covered
28
+ * part is read from `window.visualViewport`, and the bar is raised by as
29
+ * much of its own scrolling box as the keyboard covers: the keyboard's
30
+ * full height in a box that ends at the bottom of the screen, less in
31
+ * one that ends above a tab bar or inside a sheet. It never leaves its
32
+ * box. It takes a bottom margin of the same size, so the content gains
33
+ * that much room to scroll, which a short form needs to bring its last
34
+ * field above the keyboard. Where the page shrinks by itself, nothing is
35
+ * done.
36
+ *
37
+ * While it is mounted the bar also sets `scroll-padding-bottom` on its
38
+ * scrolling ancestor (or the page) to its own height plus that rise, so a
39
+ * field that takes focus is brought into view above the bar, not under
40
+ * it. Use one bar per scrolling box: two would both stick to the same
41
+ * edge, one on top of the other.
42
+ *
43
+ * In an `AppShell`, a screen with a form is a task of its own: leave the
44
+ * tab bar out (no `footer`) and let this bar be the bottom of the screen.
45
+ * With a tab bar it sits directly above it: the shell's footer lies over
46
+ * the scroller, and the bar sticks to the footer's top edge, not under it.
47
+ *
48
+ * The bar is flush with the page and page-coloured while the form ends
49
+ * where the bar is; once content is under it, it turns to frosted glass
50
+ * with a hairline 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
+ /**
64
+ * When the bar shows the glass and the hairline. `auto` follows the
65
+ * scroll position: flush at the end of the content, glass while
66
+ * content is under it. `always` is glass all the time, `never` flush
67
+ * all the time.
68
+ */
69
+ scrollEdge?: ScrollEdgeMode;
70
+ class?: string;
71
+ /** Added after the offset and margin the bar sets while the keyboard is up. */
72
+ style?: string;
73
+ /** The main action, and at most one or two beside it. */
74
+ children?: Snippet;
75
+ };
76
+ declare const StickyActionBar: import("svelte").Component<Props, {}, "">;
77
+ type StickyActionBar = ReturnType<typeof StickyActionBar>;
78
+ 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>