zabi-components 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/CHANGELOG.md +2935 -0
  2. package/README.md +243 -10
  3. package/THEME.md +162 -332
  4. package/THEMING.md +1419 -259
  5. package/dist/atoms/ActionPanel.svelte +15 -5
  6. package/dist/atoms/Avatar.svelte +120 -0
  7. package/dist/atoms/Avatar.svelte.d.ts +46 -0
  8. package/dist/atoms/Badge.svelte +71 -13
  9. package/dist/atoms/Badge.svelte.d.ts +8 -2
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +217 -30
  13. package/dist/atoms/Button.svelte.d.ts +33 -4
  14. package/dist/atoms/Card.svelte +182 -26
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/CardHeader.svelte +16 -4
  17. package/dist/atoms/Checkbox.svelte +81 -21
  18. package/dist/atoms/Checkbox.svelte.d.ts +31 -2
  19. package/dist/atoms/Chip.svelte +351 -0
  20. package/dist/atoms/Chip.svelte.d.ts +57 -0
  21. package/dist/atoms/CodeBlock.svelte +20 -2
  22. package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
  23. package/dist/atoms/CodeField.svelte +385 -0
  24. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  25. package/dist/atoms/ColorPicker.svelte +260 -49
  26. package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
  27. package/dist/atoms/Container.svelte +16 -8
  28. package/dist/atoms/DateField.svelte +145 -0
  29. package/dist/atoms/DateField.svelte.d.ts +81 -0
  30. package/dist/atoms/FieldMessages.svelte +69 -0
  31. package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
  32. package/dist/atoms/FloatingActionButton.svelte +176 -0
  33. package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
  34. package/dist/atoms/Heading.svelte +63 -5
  35. package/dist/atoms/Heading.svelte.d.ts +14 -0
  36. package/dist/atoms/IconButton.svelte +294 -26
  37. package/dist/atoms/IconButton.svelte.d.ts +62 -4
  38. package/dist/atoms/Input.svelte +259 -53
  39. package/dist/atoms/Input.svelte.d.ts +33 -2
  40. package/dist/atoms/List.svelte +5 -1
  41. package/dist/atoms/ListItem.svelte +70 -13
  42. package/dist/atoms/ListItem.svelte.d.ts +11 -0
  43. package/dist/atoms/PickerField.svelte +239 -0
  44. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  45. package/dist/atoms/Progress.svelte +99 -19
  46. package/dist/atoms/Progress.svelte.d.ts +15 -1
  47. package/dist/atoms/Radio.svelte +51 -3
  48. package/dist/atoms/Radio.svelte.d.ts +30 -2
  49. package/dist/atoms/Rating.svelte +620 -0
  50. package/dist/atoms/Rating.svelte.d.ts +77 -0
  51. package/dist/atoms/Select.svelte +438 -98
  52. package/dist/atoms/Select.svelte.d.ts +61 -3
  53. package/dist/atoms/SelectionControl.svelte +193 -23
  54. package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
  55. package/dist/atoms/Skeleton.svelte +114 -18
  56. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  57. package/dist/atoms/SkeletonGroup.svelte +43 -0
  58. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  59. package/dist/atoms/Slider.svelte +344 -0
  60. package/dist/atoms/Slider.svelte.d.ts +40 -0
  61. package/dist/atoms/Sparkline.svelte +109 -0
  62. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  63. package/dist/atoms/SpeechBubble.svelte +187 -0
  64. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  65. package/dist/atoms/Spinner.svelte +57 -0
  66. package/dist/atoms/Spinner.svelte.d.ts +19 -0
  67. package/dist/atoms/Stat.svelte +72 -0
  68. package/dist/atoms/Stat.svelte.d.ts +19 -0
  69. package/dist/atoms/Table.svelte +151 -6
  70. package/dist/atoms/Table.svelte.d.ts +15 -3
  71. package/dist/atoms/TemporalField.svelte +472 -0
  72. package/dist/atoms/TemporalField.svelte.d.ts +48 -0
  73. package/dist/atoms/Text.svelte +23 -11
  74. package/dist/atoms/Text.svelte.d.ts +6 -3
  75. package/dist/atoms/Textarea.svelte +150 -44
  76. package/dist/atoms/Textarea.svelte.d.ts +35 -2
  77. package/dist/atoms/ThemeToggle.svelte +223 -44
  78. package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
  79. package/dist/atoms/TimeColumn.svelte +150 -0
  80. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  81. package/dist/atoms/TimeField.svelte +151 -0
  82. package/dist/atoms/TimeField.svelte.d.ts +85 -0
  83. package/dist/atoms/Toast.svelte +22 -8
  84. package/dist/atoms/Toast.svelte.d.ts +2 -0
  85. package/dist/atoms/Toggle.svelte +98 -14
  86. package/dist/atoms/Toggle.svelte.d.ts +26 -2
  87. package/dist/atoms/Tooltip.svelte +528 -24
  88. package/dist/atoms/Tooltip.svelte.d.ts +18 -0
  89. package/dist/atoms/ZabiStringsProvider.svelte +45 -0
  90. package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
  91. package/dist/atoms/index.d.ts +38 -0
  92. package/dist/atoms/index.js +29 -0
  93. package/dist/atoms/selection-control.styles.d.ts +47 -5
  94. package/dist/atoms/selection-control.styles.js +52 -10
  95. package/dist/components/atoms/index.d.ts +38 -0
  96. package/dist/components/atoms/index.d.ts.map +1 -1
  97. package/dist/components/atoms/selection-control.styles.d.ts +47 -5
  98. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  99. package/dist/components/index.d.ts +104 -2
  100. package/dist/components/index.d.ts.map +1 -1
  101. package/dist/components/map.d.ts +11 -0
  102. package/dist/components/map.d.ts.map +1 -0
  103. package/dist/components/molecules/index.d.ts +51 -0
  104. package/dist/components/molecules/index.d.ts.map +1 -1
  105. package/dist/components/molecules/toast-store.d.ts +83 -7
  106. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  107. package/dist/components/organisms/index.d.ts +10 -0
  108. package/dist/components/organisms/index.d.ts.map +1 -1
  109. package/dist/components/types/variants.d.ts +24 -1
  110. package/dist/components/types/variants.d.ts.map +1 -1
  111. package/dist/components/util/app-shell.d.ts +79 -0
  112. package/dist/components/util/app-shell.d.ts.map +1 -0
  113. package/dist/components/util/avatar.d.ts +39 -0
  114. package/dist/components/util/avatar.d.ts.map +1 -0
  115. package/dist/components/util/bottom-sheet.d.ts +30 -0
  116. package/dist/components/util/bottom-sheet.d.ts.map +1 -0
  117. package/dist/components/util/bottom-tab-bar.d.ts +41 -0
  118. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
  119. package/dist/components/util/calendar.d.ts +56 -0
  120. package/dist/components/util/calendar.d.ts.map +1 -0
  121. package/dist/components/util/chart.d.ts +140 -0
  122. package/dist/components/util/chart.d.ts.map +1 -0
  123. package/dist/components/util/chip.d.ts +66 -0
  124. package/dist/components/util/chip.d.ts.map +1 -0
  125. package/dist/components/util/cn.d.ts.map +1 -1
  126. package/dist/components/util/code-field.d.ts +63 -0
  127. package/dist/components/util/code-field.d.ts.map +1 -0
  128. package/dist/components/util/collapsible.d.ts +64 -0
  129. package/dist/components/util/collapsible.d.ts.map +1 -0
  130. package/dist/components/util/confirm-dialog.d.ts +12 -0
  131. package/dist/components/util/confirm-dialog.d.ts.map +1 -0
  132. package/dist/components/util/date.d.ts +96 -0
  133. package/dist/components/util/date.d.ts.map +1 -0
  134. package/dist/components/util/display-voice.d.ts +10 -0
  135. package/dist/components/util/display-voice.d.ts.map +1 -0
  136. package/dist/components/util/drawer.d.ts +13 -0
  137. package/dist/components/util/drawer.d.ts.map +1 -0
  138. package/dist/components/util/dropdown.d.ts +47 -0
  139. package/dist/components/util/dropdown.d.ts.map +1 -0
  140. package/dist/components/util/field-control.d.ts +30 -0
  141. package/dist/components/util/field-control.d.ts.map +1 -0
  142. package/dist/components/util/field.d.ts +45 -0
  143. package/dist/components/util/field.d.ts.map +1 -0
  144. package/dist/components/util/fit-in-viewport.d.ts +201 -0
  145. package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
  146. package/dist/components/util/focus-utils.d.ts +77 -2
  147. package/dist/components/util/focus-utils.d.ts.map +1 -1
  148. package/dist/components/util/hydration.d.ts +45 -0
  149. package/dist/components/util/hydration.d.ts.map +1 -0
  150. package/dist/components/util/keyboard-inset.d.ts +18 -0
  151. package/dist/components/util/keyboard-inset.d.ts.map +1 -0
  152. package/dist/components/util/map-view.d.ts +144 -0
  153. package/dist/components/util/map-view.d.ts.map +1 -0
  154. package/dist/components/util/media-grid.d.ts +51 -0
  155. package/dist/components/util/media-grid.d.ts.map +1 -0
  156. package/dist/components/util/media.d.ts +6 -0
  157. package/dist/components/util/media.d.ts.map +1 -0
  158. package/dist/components/util/overlay-chrome.d.ts +53 -0
  159. package/dist/components/util/overlay-chrome.d.ts.map +1 -0
  160. package/dist/components/util/overlay.d.ts +72 -0
  161. package/dist/components/util/overlay.d.ts.map +1 -0
  162. package/dist/components/util/photo.d.ts +234 -0
  163. package/dist/components/util/photo.d.ts.map +1 -0
  164. package/dist/components/util/portal.d.ts +23 -0
  165. package/dist/components/util/portal.d.ts.map +1 -0
  166. package/dist/components/util/progress.d.ts +19 -0
  167. package/dist/components/util/progress.d.ts.map +1 -0
  168. package/dist/components/util/pull-to-refresh.d.ts +27 -0
  169. package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
  170. package/dist/components/util/radio-keys.d.ts +25 -0
  171. package/dist/components/util/radio-keys.d.ts.map +1 -0
  172. package/dist/components/util/rating.d.ts +23 -0
  173. package/dist/components/util/rating.d.ts.map +1 -0
  174. package/dist/components/util/ready-made-strings.d.ts +77 -0
  175. package/dist/components/util/ready-made-strings.d.ts.map +1 -0
  176. package/dist/components/util/scroll-edge.d.ts +33 -0
  177. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  178. package/dist/components/util/scroll-padding.d.ts +20 -0
  179. package/dist/components/util/scroll-padding.d.ts.map +1 -0
  180. package/dist/components/util/segmented-control.d.ts +16 -0
  181. package/dist/components/util/segmented-control.d.ts.map +1 -0
  182. package/dist/components/util/select.d.ts +41 -0
  183. package/dist/components/util/select.d.ts.map +1 -0
  184. package/dist/components/util/sheet-drag.d.ts +84 -0
  185. package/dist/components/util/sheet-drag.d.ts.map +1 -0
  186. package/dist/components/util/sidebar.d.ts +63 -0
  187. package/dist/components/util/sidebar.d.ts.map +1 -0
  188. package/dist/components/util/skeleton.d.ts +15 -0
  189. package/dist/components/util/skeleton.d.ts.map +1 -0
  190. package/dist/components/util/sliding-indicator.d.ts +25 -0
  191. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  192. package/dist/components/util/sortable-list.d.ts +65 -0
  193. package/dist/components/util/sortable-list.d.ts.map +1 -0
  194. package/dist/components/util/stepper.d.ts +40 -0
  195. package/dist/components/util/stepper.d.ts.map +1 -0
  196. package/dist/components/util/swipeable-list-item.d.ts +36 -0
  197. package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
  198. package/dist/components/util/temporal-field.d.ts +89 -0
  199. package/dist/components/util/temporal-field.d.ts.map +1 -0
  200. package/dist/components/util/theme-mode.d.ts +80 -0
  201. package/dist/components/util/theme-mode.d.ts.map +1 -0
  202. package/dist/components/util/toaster.d.ts +133 -0
  203. package/dist/components/util/toaster.d.ts.map +1 -0
  204. package/dist/components/util/tone-surface.d.ts +34 -0
  205. package/dist/components/util/tone-surface.d.ts.map +1 -0
  206. package/dist/components/util/tooltip.d.ts +10 -0
  207. package/dist/components/util/tooltip.d.ts.map +1 -0
  208. package/dist/components/util/top-navbar.d.ts +35 -0
  209. package/dist/components/util/top-navbar.d.ts.map +1 -0
  210. package/dist/components/util/touch-target.d.ts +31 -0
  211. package/dist/components/util/touch-target.d.ts.map +1 -0
  212. package/dist/components/util/zabi-strings.d.ts +142 -0
  213. package/dist/components/util/zabi-strings.d.ts.map +1 -0
  214. package/dist/create-theme/cli.js +173 -0
  215. package/dist/create-theme/index.d.ts +190 -0
  216. package/dist/create-theme/index.js +1034 -0
  217. package/dist/create-theme/lib/ramp-math.js +128 -0
  218. package/dist/create-theme/lib/resolve.js +238 -0
  219. package/dist/create-theme/theme-data.js +2644 -0
  220. package/dist/index.d.ts +104 -2
  221. package/dist/index.js +75 -2
  222. package/dist/map.d.ts +10 -0
  223. package/dist/map.js +9 -0
  224. package/dist/molecules/Alert.svelte +63 -16
  225. package/dist/molecules/Alert.svelte.d.ts +14 -1
  226. package/dist/molecules/AppBar.svelte +674 -0
  227. package/dist/molecules/AppBar.svelte.d.ts +131 -0
  228. package/dist/molecules/AvatarGroup.svelte +116 -0
  229. package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
  230. package/dist/molecules/BarChart.svelte +381 -0
  231. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  232. package/dist/molecules/BottomSheet.svelte +608 -0
  233. package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
  234. package/dist/molecules/BottomTabBar.svelte +403 -0
  235. package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
  236. package/dist/molecules/Calendar.svelte +597 -0
  237. package/dist/molecules/Calendar.svelte.d.ts +72 -0
  238. package/dist/molecules/ChipGroup.svelte +325 -0
  239. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  240. package/dist/molecules/Collapsible.svelte +347 -0
  241. package/dist/molecules/Collapsible.svelte.d.ts +61 -0
  242. package/dist/molecules/CollapsibleGroup.svelte +136 -0
  243. package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
  244. package/dist/molecules/ComponentDemo.svelte +16 -4
  245. package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
  246. package/dist/molecules/ConfirmDialog.svelte +257 -0
  247. package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
  248. package/dist/molecules/ContactForm.svelte +34 -17
  249. package/dist/molecules/ContactForm.svelte.d.ts +8 -0
  250. package/dist/molecules/Drawer.svelte +415 -0
  251. package/dist/molecules/Drawer.svelte.d.ts +55 -0
  252. package/dist/molecules/Dropdown.svelte +517 -48
  253. package/dist/molecules/Dropdown.svelte.d.ts +47 -6
  254. package/dist/molecules/DropdownItem.svelte +122 -0
  255. package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
  256. package/dist/molecules/EmptyState.svelte +55 -10
  257. package/dist/molecules/EmptyState.svelte.d.ts +8 -0
  258. package/dist/molecules/FormField.svelte +9 -1
  259. package/dist/molecules/FormField.svelte.d.ts +2 -0
  260. package/dist/molecules/Header.svelte +1 -1
  261. package/dist/molecules/ImageUpload.svelte +492 -69
  262. package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
  263. package/dist/molecules/LineChart.svelte +459 -0
  264. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  265. package/dist/molecules/MapView.svelte +565 -0
  266. package/dist/molecules/MapView.svelte.d.ts +88 -0
  267. package/dist/molecules/MediaGrid.svelte +527 -0
  268. package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
  269. package/dist/molecules/Modal.svelte +293 -72
  270. package/dist/molecules/Modal.svelte.d.ts +51 -2
  271. package/dist/molecules/NavigationMenuContent.svelte +58 -1
  272. package/dist/molecules/NavigationMenuItem.svelte +0 -1
  273. package/dist/molecules/NavigationMenuLink.svelte +1 -1
  274. package/dist/molecules/NavigationMenuList.svelte +10 -3
  275. package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
  276. package/dist/molecules/Page.svelte +32 -2
  277. package/dist/molecules/Page.svelte.d.ts +11 -0
  278. package/dist/molecules/PhotoGrid.svelte +523 -0
  279. package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
  280. package/dist/molecules/PhotoViewer.svelte +1213 -0
  281. package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
  282. package/dist/molecules/PieChart.svelte +421 -0
  283. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  284. package/dist/molecules/PropsTable.svelte +22 -10
  285. package/dist/molecules/PropsTable.svelte.d.ts +3 -0
  286. package/dist/molecules/PullToRefresh.svelte +263 -0
  287. package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
  288. package/dist/molecules/RadioGroup.svelte +72 -18
  289. package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
  290. package/dist/molecules/Section.svelte +1 -1
  291. package/dist/molecules/SegmentedControl.svelte +456 -0
  292. package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
  293. package/dist/molecules/SidebarBrandHeader.svelte +11 -1
  294. package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
  295. package/dist/molecules/SidebarFooter.svelte +30 -7
  296. package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
  297. package/dist/molecules/SidebarNavSection.svelte +1 -1
  298. package/dist/molecules/SlideUp.svelte +220 -61
  299. package/dist/molecules/SlideUp.svelte.d.ts +26 -0
  300. package/dist/molecules/SortableList.svelte +633 -0
  301. package/dist/molecules/SortableList.svelte.d.ts +69 -0
  302. package/dist/molecules/Stepper.svelte +562 -0
  303. package/dist/molecules/Stepper.svelte.d.ts +39 -0
  304. package/dist/molecules/StickyActionBar.svelte +250 -0
  305. package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
  306. package/dist/molecules/SwipeableListItem.svelte +334 -0
  307. package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
  308. package/dist/molecules/Tabs.svelte +310 -9
  309. package/dist/molecules/Tabs.svelte.d.ts +7 -0
  310. package/dist/molecules/Toaster.svelte +514 -5
  311. package/dist/molecules/Toaster.svelte.d.ts +153 -2
  312. package/dist/molecules/ToasterToast.svelte +495 -77
  313. package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
  314. package/dist/molecules/UnsavedChangesBar.svelte +223 -0
  315. package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
  316. package/dist/molecules/index.d.ts +51 -0
  317. package/dist/molecules/index.js +31 -0
  318. package/dist/molecules/toast-store.d.ts +83 -7
  319. package/dist/molecules/toast-store.js +25 -1
  320. package/dist/organisms/AppNavigation.svelte +440 -0
  321. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  322. package/dist/organisms/AppShell.svelte +696 -0
  323. package/dist/organisms/AppShell.svelte.d.ts +157 -0
  324. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  325. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  326. package/dist/organisms/SidebarAccountPanel.svelte +107 -12
  327. package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
  328. package/dist/organisms/SidebarNavigation.svelte +111 -43
  329. package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
  330. package/dist/organisms/SidebarPanel.svelte +34 -17
  331. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  332. package/dist/organisms/SidebarShell.svelte +164 -14
  333. package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
  334. package/dist/organisms/TopNavbar.svelte +353 -39
  335. package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
  336. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  337. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  338. package/dist/organisms/index.d.ts +10 -0
  339. package/dist/organisms/index.js +7 -0
  340. package/dist/types/components.d.ts +21 -1
  341. package/dist/types/components.d.ts.map +1 -1
  342. package/dist/types/index.d.ts +1093 -26
  343. package/dist/types/index.d.ts.map +1 -1
  344. package/dist/types/variants.ts +28 -1
  345. package/dist/util/app-shell.d.ts +78 -0
  346. package/dist/util/app-shell.js +97 -0
  347. package/dist/util/avatar.d.ts +38 -0
  348. package/dist/util/avatar.js +52 -0
  349. package/dist/util/bottom-sheet.d.ts +29 -0
  350. package/dist/util/bottom-sheet.js +43 -0
  351. package/dist/util/bottom-tab-bar.d.ts +40 -0
  352. package/dist/util/bottom-tab-bar.js +54 -0
  353. package/dist/util/calendar.d.ts +55 -0
  354. package/dist/util/calendar.js +74 -0
  355. package/dist/util/chart.d.ts +139 -0
  356. package/dist/util/chart.js +305 -0
  357. package/dist/util/chip.d.ts +65 -0
  358. package/dist/util/chip.js +58 -0
  359. package/dist/util/cn.js +28 -2
  360. package/dist/util/code-field.d.ts +62 -0
  361. package/dist/util/code-field.js +112 -0
  362. package/dist/util/collapsible.d.ts +63 -0
  363. package/dist/util/collapsible.js +23 -0
  364. package/dist/util/confirm-dialog.d.ts +11 -0
  365. package/dist/util/confirm-dialog.js +2 -0
  366. package/dist/util/date.d.ts +95 -0
  367. package/dist/util/date.js +237 -0
  368. package/dist/util/display-voice.d.ts +9 -0
  369. package/dist/util/display-voice.js +9 -0
  370. package/dist/util/drawer.d.ts +12 -0
  371. package/dist/util/drawer.js +7 -0
  372. package/dist/util/dropdown.d.ts +46 -0
  373. package/dist/util/dropdown.js +21 -0
  374. package/dist/util/field-control.d.ts +29 -0
  375. package/dist/util/field-control.js +32 -0
  376. package/dist/util/field.d.ts +44 -0
  377. package/dist/util/field.js +38 -0
  378. package/dist/util/fit-in-viewport.d.ts +200 -0
  379. package/dist/util/fit-in-viewport.js +399 -0
  380. package/dist/util/focus-utils.d.ts +77 -2
  381. package/dist/util/focus-utils.js +186 -9
  382. package/dist/util/hydration.d.ts +44 -0
  383. package/dist/util/hydration.js +58 -0
  384. package/dist/util/keyboard-inset.d.ts +17 -0
  385. package/dist/util/keyboard-inset.js +53 -0
  386. package/dist/util/map-view.d.ts +143 -0
  387. package/dist/util/map-view.js +121 -0
  388. package/dist/util/media-grid.d.ts +50 -0
  389. package/dist/util/media-grid.js +81 -0
  390. package/dist/util/media.d.ts +5 -0
  391. package/dist/util/media.js +10 -0
  392. package/dist/util/overlay-chrome.d.ts +52 -0
  393. package/dist/util/overlay-chrome.js +56 -0
  394. package/dist/util/overlay.d.ts +71 -0
  395. package/dist/util/overlay.js +240 -0
  396. package/dist/util/photo.d.ts +233 -0
  397. package/dist/util/photo.js +209 -0
  398. package/dist/util/portal.d.ts +22 -0
  399. package/dist/util/portal.js +50 -0
  400. package/dist/util/progress.d.ts +18 -0
  401. package/dist/util/progress.js +17 -0
  402. package/dist/util/pull-to-refresh.d.ts +26 -0
  403. package/dist/util/pull-to-refresh.js +30 -0
  404. package/dist/util/radio-keys.d.ts +24 -0
  405. package/dist/util/radio-keys.js +49 -0
  406. package/dist/util/rating.d.ts +22 -0
  407. package/dist/util/rating.js +26 -0
  408. package/dist/util/ready-made-strings.d.ts +76 -0
  409. package/dist/util/ready-made-strings.js +67 -0
  410. package/dist/util/scroll-edge.d.ts +32 -0
  411. package/dist/util/scroll-edge.js +87 -0
  412. package/dist/util/scroll-padding.d.ts +19 -0
  413. package/dist/util/scroll-padding.js +44 -0
  414. package/dist/util/segmented-control.d.ts +15 -0
  415. package/dist/util/segmented-control.js +1 -0
  416. package/dist/util/select.d.ts +40 -0
  417. package/dist/util/select.js +63 -0
  418. package/dist/util/sheet-drag.d.ts +83 -0
  419. package/dist/util/sheet-drag.js +311 -0
  420. package/dist/util/sidebar.d.ts +62 -0
  421. package/dist/util/sidebar.js +38 -0
  422. package/dist/util/skeleton.d.ts +14 -0
  423. package/dist/util/skeleton.js +22 -0
  424. package/dist/util/sliding-indicator.d.ts +24 -0
  425. package/dist/util/sliding-indicator.js +44 -0
  426. package/dist/util/sortable-list.d.ts +64 -0
  427. package/dist/util/sortable-list.js +53 -0
  428. package/dist/util/stepper.d.ts +39 -0
  429. package/dist/util/stepper.js +38 -0
  430. package/dist/util/swipeable-list-item.d.ts +35 -0
  431. package/dist/util/swipeable-list-item.js +32 -0
  432. package/dist/util/temporal-field.d.ts +88 -0
  433. package/dist/util/temporal-field.js +136 -0
  434. package/dist/util/theme-mode.d.ts +79 -0
  435. package/dist/util/theme-mode.js +121 -0
  436. package/dist/util/toaster.d.ts +132 -0
  437. package/dist/util/toaster.js +130 -0
  438. package/dist/util/tone-surface.d.ts +33 -0
  439. package/dist/util/tone-surface.js +35 -0
  440. package/dist/util/tooltip.d.ts +9 -0
  441. package/dist/util/tooltip.js +18 -0
  442. package/dist/util/top-navbar.d.ts +34 -0
  443. package/dist/util/top-navbar.js +28 -0
  444. package/dist/util/touch-target.d.ts +30 -0
  445. package/dist/util/touch-target.js +30 -0
  446. package/dist/util/zabi-strings.d.ts +141 -0
  447. package/dist/util/zabi-strings.js +105 -0
  448. package/dist/zabi-components-colors.css +11 -2
  449. package/dist/zabi-components-theme-dark-only.css +870 -258
  450. package/dist/zabi-components-theme-dark.css +870 -258
  451. package/dist/zabi-components-theme-only.css +2419 -368
  452. package/dist/zabi-components-theme.css +2419 -368
  453. package/dist/zabi-components.css +6032 -711
  454. package/docs/lucide-icons.md +40 -17
  455. package/docs/theme-imports.md +81 -11
  456. package/package.json +45 -8
@@ -1,22 +1,531 @@
1
1
  <script lang="ts">
2
+ import { mergeStrings } from "../util/ready-made-strings.js";
3
+ import { zabiStringsFor } from "../util/zabi-strings.js";
2
4
  import ToasterToast from './ToasterToast.svelte';
3
5
  import { toastStore } from './toast-store.js';
4
6
  import { cn } from "../util/cn.js";
7
+ import { readKeyboardInset } from "../util/keyboard-inset.js";
8
+ import { topOverlayFooter, topOverlayHeader, watchOverlayFooters } from "../util/overlay.js";
9
+ import {
10
+ DEFAULT_TOASTER_STRINGS,
11
+ toastsReplaced,
12
+ toastStays,
13
+ type ToastDuration,
14
+ type ToasterMode,
15
+ type ToasterStrings,
16
+ type ToastPauseChange,
17
+ } from "../util/toaster.js";
18
+ import type { HTMLAttributes } from "svelte/elements";
5
19
 
6
- interface Props {
20
+ /**
21
+ * The stack of toasts, fixed to the bottom of the screen: in the corner
22
+ * from 640px up, across the width with 16px on each side below that.
23
+ *
24
+ * It keeps clear of what is at the bottom of a phone screen. The home
25
+ * indicator and a notch at the side are read from the safe-area insets
26
+ * (the page has to ask for them with `viewport-fit=cover`). Inside an
27
+ * `AppShell` the stack sits above the shell's tab bar, which the shell
28
+ * reports in `--app-shell-bottom-inset`. For anything else fixed to the
29
+ * bottom (a `BottomTabBar` on its own, a `StickyActionBar`, a
30
+ * `FloatingActionButton`), say how tall it is with
31
+ * `--toaster-bottom-offset` on the Toaster or any ancestor:
32
+ *
33
+ * ```svelte
34
+ * <Toaster style="--toaster-bottom-offset: 64px" />
35
+ * ```
36
+ *
37
+ * A `FloatingActionButton` is 56px with 16px under it: `72px` puts the
38
+ * stack above it.
39
+ *
40
+ * That offset is for what is on the page. Over a modal overlay (Modal,
41
+ * BottomSheet, SlideUp, Drawer) the stack minds the overlay instead: it
42
+ * never lies on the header, which holds the close button, nor on a
43
+ * footer pinned to the bottom, and the offset is not added on top. It
44
+ * sits above the panel when there is more room there, and otherwise
45
+ * between the header and the footer. Where the on-screen keyboard covers
46
+ * the page, the stack sits above the keyboard. It is never taller than
47
+ * the room it has: with more toasts than fit, it scrolls. With an
48
+ * overlay open, Tab goes from its last control into the toasts and back,
49
+ * and Escape in a toast returns focus to where it came from.
50
+ *
51
+ * A toast shows what it was pushed with: its `title`, its `message`
52
+ * under it, or the message alone.
53
+ *
54
+ * How long it stays is said in words, per toast (`pushToast({ duration:
55
+ * "short" })`) or for all of them here (`defaultDuration`): `"short"` is
56
+ * 3 seconds, for a few words that need no reading time ("Saved");
57
+ * anything a person has to read is `"medium"` (7 seconds) or `"long"`
58
+ * (14); what they have to act on, and an error, should stay:
59
+ * `"persistent"`. A number is milliseconds. A toast given no duration
60
+ * gets medium up to 120 characters of text, long up to 240, and stays
61
+ * beyond that; an error and a toast with an action stay until they are
62
+ * dismissed.
63
+ *
64
+ * Its timer stops while a mouse is over it, a finger is on it or focus
65
+ * is inside it, which is what lets a timed toast be read in the reader's
66
+ * own time (WCAG 2.2.1). A finger that lifts leaves it at least three
67
+ * seconds, so holding a short toast starts it again.
68
+ * `data-paused` on the toast says so, and `onpausechange` is called.
69
+ * A mouse holds the timer once it has moved over the toast, not when the
70
+ * toast comes up under a pointer that is at rest. A touch or a pen drags
71
+ * a toast away sideways (`swipeToDismiss`, on by default), and the close
72
+ * button stays as the way for a keyboard and a screen reader. A toast
73
+ * pushed with the id of one that is showing replaces it in place, and
74
+ * `max` limits how many show at once: the oldest toast that is timed
75
+ * (not an error, not one with an action) is the one that leaves.
76
+ *
77
+ * For a phone, `mode="auto"` makes the toast a snackbar below 640px: one
78
+ * row (two lines at most), one at a time, with its action beside its text
79
+ * and no stack. It sits directly above what is docked at the bottom, which
80
+ * it finds by itself: a `BottomTabBar`, a `StickyActionBar`, a
81
+ * `FloatingActionButton` and an `UnsavedChangesBar` that lie under it, and
82
+ * anything you name in `avoid` (the button a screen ends in). It comes out
83
+ * from behind that edge and goes back into it, on `--ease-standard`, with
84
+ * no movement under reduced motion, and lies over the page without moving
85
+ * it. Push a short confirmation with `closable: false` and it has no
86
+ * control and takes no taps.
87
+ *
88
+ * ```svelte
89
+ * <Toaster mode="auto" avoid="[data-primary-action]" />
90
+ * ```
91
+ *
92
+ * Every word the toaster says by itself is in `strings`:
93
+ *
94
+ * ```svelte
95
+ * <Toaster
96
+ * strings={{
97
+ * regionLabel: "Aviseringar",
98
+ * dismiss: "Stäng aviseringen",
99
+ * closesIn: (seconds) => `Stängs om ${seconds} sekunder.`,
100
+ * }}
101
+ * />
102
+ * ```
103
+ */
104
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-label"> & {
105
+ /**
106
+ * The words the toaster says by itself, for an app in another
107
+ * language: the region's name, the buttons' names, the sentence about
108
+ * the time left. What is left out keeps its English default.
109
+ */
110
+ strings?: Partial<ToasterStrings>;
111
+ /** Accessible name of the region. Wins over `strings.regionLabel`. */
112
+ "aria-label"?: string;
113
+ /**
114
+ * Shows the time a toast has left as a sentence under it, with a
115
+ * button that stops the timer. Without it the bar along the bottom of
116
+ * the toast shows the time, and the sentence is there for a screen
117
+ * reader only.
118
+ */
119
+ showCountdown?: boolean;
120
+ /**
121
+ * How long a toast stays when it was pushed without a `duration`, in
122
+ * place of `"medium"`: `"short"`, `"medium"`, `"long"`,
123
+ * `"persistent"` or milliseconds. An error and a toast with an action
124
+ * still stay until dismissed, a text of 121 to 240 characters still
125
+ * gets at least `"long"`, and a longer one still stays. A `duration`
126
+ * on the toast wins.
127
+ */
128
+ defaultDuration?: ToastDuration;
129
+ /**
130
+ * `"stack"` (default): the stack of toasts. `"snackbar"`: the compact
131
+ * form for a phone, at every width. `"auto"`: the snackbar below
132
+ * 640px, the stack from there up.
133
+ */
134
+ mode?: ToasterMode;
135
+ /**
136
+ * How many toasts show at once. The oldest one that is timed leaves
137
+ * when a newer one arrives; a toast that has to stay (an error, one
138
+ * with an action, `"persistent"`) is not dropped, so there can be more
139
+ * than this while they are showing. Default: no limit, and 1 in the
140
+ * snackbar.
141
+ */
142
+ max?: number;
143
+ /**
144
+ * A CSS selector for elements the stack must not lie on, in addition
145
+ * to the docked bars it finds by itself: the button a screen ends in.
146
+ * When one lies under the stack, the stack sits above it.
147
+ */
148
+ avoid?: string;
149
+ /**
150
+ * Draws the thin bar along the bottom of a toast that empties over its
151
+ * time. Unset: drawn, except under a confirmation in the snackbar that
152
+ * has nothing to press (pushed with `closable: false`), where it is
153
+ * left out. `false` leaves it out everywhere, `true` draws it
154
+ * everywhere; a toast's own `timeLine` wins. The sentence for a screen
155
+ * reader about the time left stays.
156
+ */
157
+ timeLine?: boolean;
158
+ /**
159
+ * Touch and pen drag a toast away sideways (and down, in the
160
+ * snackbar). Default `true`. A mouse does not drag; the close button
161
+ * is the way for a keyboard and a screen reader.
162
+ */
163
+ swipeToDismiss?: boolean;
164
+ /**
165
+ * Called when a pointer, a finger or focus starts or stops holding a
166
+ * toast's timer, with the toast's id. The same state is `data-paused`
167
+ * on the toast.
168
+ */
169
+ onpausechange?: (detail: ToastPauseChange) => void;
7
170
  class?: string;
171
+ };
172
+
173
+ let {
174
+ strings,
175
+ "aria-label": ariaLabel,
176
+ showCountdown = false,
177
+ defaultDuration,
178
+ mode = "stack",
179
+ max,
180
+ avoid,
181
+ swipeToDismiss = true,
182
+ timeLine,
183
+ onpausechange,
184
+ class: className = '',
185
+ ...restProps
186
+ }: Props = $props();
187
+
188
+ /** The app-wide words for the toaster, from the `ZabiStringsProvider` it is mounted under, if there is one. */
189
+ const provided = zabiStringsFor("toaster");
190
+ const words = $derived<ToasterStrings>(mergeStrings(DEFAULT_TOASTER_STRINGS, provided(), strings));
191
+
192
+ /**
193
+ * One utility per property, so a class from the caller still replaces it
194
+ * at every width; what differs below 640px is carried by the three
195
+ * variables. From 640px up this is what it always was: 1rem from the
196
+ * bottom and the right, 24rem wide, 1rem of padding.
197
+ */
198
+ const placement = [
199
+ "[--toaster-edge:0px] sm:[--toaster-edge:1rem]",
200
+ "[--toaster-width:100vw] sm:[--toaster-width:24rem]",
201
+ // In px below 640px: a gutter that grew with the text would take the room the text needs.
202
+ "[--toaster-padding:16px] sm:[--toaster-padding:1rem]",
203
+ // `--toaster-overlay-inset` is set below, while a footer of an open overlay is under the stack.
204
+ "bottom-[calc(max(max(var(--app-shell-bottom-inset,0px),env(safe-area-inset-bottom,0px))_+_var(--toaster-bottom-offset,0px),var(--toaster-overlay-inset,0px))_+_var(--toaster-edge))]",
205
+ "right-[calc(env(safe-area-inset-right,0px)_+_var(--toaster-edge))]",
206
+ "w-[min(var(--toaster-width),calc(100vw_-_env(safe-area-inset-left,0px)_-_env(safe-area-inset-right,0px)_-_2_*_var(--toaster-edge)))]",
207
+ "p-(--toaster-padding)",
208
+ ].join(" ");
209
+
210
+ let region = $state<HTMLDivElement>();
211
+
212
+ /** Below 640px (`sm`), for `mode="auto"`. */
213
+ let narrow = $state(false);
214
+ $effect(() => {
215
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
216
+ const query = window.matchMedia("(max-width: 39.999rem)");
217
+ const update = () => (narrow = query.matches);
218
+ update();
219
+ query.addEventListener?.("change", update);
220
+ return () => query.removeEventListener?.("change", update);
221
+ });
222
+ const snackbar = $derived(mode === "snackbar" || (mode === "auto" && narrow));
223
+
224
+ /**
225
+ * Cuts at the edge the toast is docked on, so it comes out from behind it (sides and top stay open).
226
+ * Each class is written out whole: Tailwind only generates what it finds as text in the source, so a
227
+ * class put together from a prefix and a value at run time is in no stylesheet.
228
+ */
229
+ const clipClass = $derived(
230
+ mode === "snackbar"
231
+ ? "[clip-path:inset(-100vh_-100vw_0_-100vw)]"
232
+ : mode === "auto"
233
+ ? "max-sm:[clip-path:inset(-100vh_-100vw_0_-100vw)]"
234
+ : "",
235
+ );
236
+
237
+ /** The oldest toast that is timed leaves for a newer one; one that has to stay is never dropped. */
238
+ const limit = $derived(max ?? (snackbar ? 1 : Infinity));
239
+ $effect(() => {
240
+ const list = $toastStore;
241
+ const allowed = Math.floor(limit);
242
+ let over = list.length - allowed;
243
+ if (!(allowed >= 1) || over <= 0) return;
244
+ for (const toast of list.slice(0, -1)) {
245
+ if (over <= 0) break;
246
+ if (toastStays(toast, defaultDuration)) continue;
247
+ toastsReplaced.add(toast.id);
248
+ toastStore.dismiss(toast.id);
249
+ over -= 1;
250
+ }
251
+ });
252
+
253
+ /** The highest the stack may reach: below the status bar or an AppShell's top bar. */
254
+ const TOP = "calc(max(var(--app-shell-top-inset, 0px), env(safe-area-inset-top, 0px)) + var(--toaster-edge))";
255
+ /** What is docked at the bottom, and so may lie under the stack. */
256
+ const DOCKED = '[data-bar-edge="bottom"], [data-toast-clear]';
257
+
258
+ /**
259
+ * The top edge of the highest docked element (or one named in `avoid`)
260
+ * that the stack at rest would lie on, or null when it lies on none.
261
+ */
262
+ function dockedTop(rest: DOMRect): number | null {
263
+ let nodes: NodeListOf<HTMLElement>;
264
+ try {
265
+ nodes = document.querySelectorAll<HTMLElement>(avoid ? `${DOCKED}, ${avoid}` : DOCKED);
266
+ } catch {
267
+ // A selector that does not parse is not a reason to lose the toast.
268
+ return null;
269
+ }
270
+ let top: number | null = null;
271
+ for (const node of nodes) {
272
+ if (node.closest("[data-zabi-toaster]")) continue;
273
+ const box = node.getBoundingClientRect();
274
+ if (box.width === 0 || box.height === 0) continue;
275
+ // Only what is in the same columns, and under the stack at rest.
276
+ if (box.left >= rest.right || box.right <= rest.left) continue;
277
+ if (box.top >= rest.bottom || box.bottom <= rest.top) continue;
278
+ top = top === null ? box.top : Math.min(top, box.top);
279
+ }
280
+ return top;
8
281
  }
9
282
 
10
- let { class: className = '' }: Props = $props();
283
+ /**
284
+ * Places the stack for what is on screen now. At rest it is where the
285
+ * classes put it, raised above the on-screen keyboard where that covers
286
+ * the page. The header (with its close button) and the pinned footer of
287
+ * the overlay on top are never covered: when the stack would lie on
288
+ * either, it takes the larger of the two free stretches, above the panel
289
+ * or between its header and its footer. Where it would be taller than
290
+ * the room it has, it gets that height and scrolls, with the newest toast
291
+ * in view.
292
+ * Written straight to the element: it is measured between the steps, in
293
+ * one go, before anything is painted.
294
+ */
295
+ function layout(): void {
296
+ const stack = region;
297
+ if (!stack) return;
298
+ // Taking the height off puts a stack that scrolls back at its start.
299
+ const scrolledTo = stack.style.overflowY === "auto" ? stack.scrollTop : null;
300
+ stack.style.removeProperty("--toaster-overlay-inset");
301
+ stack.style.top = "";
302
+ stack.style.bottom = "";
303
+ stack.style.maxHeight = "";
304
+ stack.style.overflowY = "";
305
+ stack.style.paddingBlock = "";
306
+ if (stack.childElementCount === 0) return;
307
+
308
+ const viewportHeight = document.documentElement.clientHeight;
309
+ // Nothing where the page shrinks by itself: the classes already end above the keyboard there.
310
+ const keyboard = readKeyboardInset();
311
+ const raise = (px: number) => stack.style.setProperty("--toaster-overlay-inset", `${px}px`);
312
+ if (keyboard > 0) raise(keyboard);
313
+ let rest = stack.getBoundingClientRect();
314
+ // No layout (a test DOM): nothing to place.
315
+ if (rest.width === 0 && rest.height === 0) return;
316
+
317
+ // Docked bars and named elements that lie under the stack: it sits above the highest.
318
+ const dockTop = dockedTop(rest);
319
+ if (dockTop !== null) {
320
+ raise(Math.max(keyboard, Math.ceil(viewportHeight - dockTop)));
321
+ rest = stack.getBoundingClientRect();
322
+ }
323
+
324
+ stack.style.top = TOP;
325
+ stack.style.bottom = "auto";
326
+ const topLimit = stack.getBoundingClientRect().top;
327
+ stack.style.top = "";
328
+ stack.style.bottom = "";
329
+
330
+ // Only what is in the same columns as the stack can be under it.
331
+ const beside = (part: HTMLElement | null) => {
332
+ const box = part?.getBoundingClientRect();
333
+ return box && box.height > 0 && box.left < rest.right && box.right > rest.left ? box : null;
334
+ };
335
+ const header = beside(topOverlayHeader());
336
+ const footer = beside(topOverlayFooter());
337
+ const under = (box: DOMRect | null) => !!box && box.top < rest.bottom && box.bottom > rest.top;
338
+
339
+ let ceiling = topLimit;
340
+ let bottomEdge = rest.bottom;
341
+ if (under(header) || under(footer)) {
342
+ // Between the header and the footer (or the stack's own resting edge)...
343
+ const insideTop = header ? Math.max(topLimit, header.bottom) : topLimit;
344
+ const insideBottom = footer && footer.top > insideTop ? footer.top : rest.bottom;
345
+ // ...or above the panel, over the backdrop.
346
+ const aboveBottom = header ? header.top : topLimit;
347
+ const above = aboveBottom - topLimit > insideBottom - insideTop;
348
+ ceiling = above ? topLimit : insideTop;
349
+ const floor = above ? aboveBottom : insideBottom;
350
+ if (floor < rest.bottom) raise(Math.max(keyboard, Math.ceil(viewportHeight - floor)));
351
+ bottomEdge = stack.getBoundingClientRect().bottom;
352
+ } else if (header && header.bottom <= rest.top) {
353
+ // Clear of it now; it does not grow over it either.
354
+ ceiling = Math.max(topLimit, header.bottom);
355
+ }
356
+
357
+ const room = Math.max(0, Math.floor(bottomEdge - ceiling));
358
+ if (stack.scrollHeight > room + 1) {
359
+ stack.style.maxHeight = `${room}px`;
360
+ stack.style.overflowY = "auto";
361
+ // A box is never shorter than its own padding. In a slit between
362
+ // a header and a footer (a small dialog over a keyboard, on a
363
+ // phone held sideways) the padding goes, or the stack would reach
364
+ // over the header after all.
365
+ const padding = parseFloat(getComputedStyle(stack).paddingTop) || 0;
366
+ if (room < 2 * padding + 44) stack.style.paddingBlock = "0px";
367
+ // Where it was, or at its end when it has only now begun to scroll or was at its
368
+ // end before: the newest toast is the last one.
369
+ const atEnd = scrolledTo !== null && scrolledTo >= endAt - 1;
370
+ stack.scrollTop = scrolledTo === null || atEnd ? stack.scrollHeight : scrolledTo;
371
+ endAt = stack.scrollHeight - stack.clientHeight;
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Where the end of a stack that scrolls was, at the last layout. A toast's words arrive a
377
+ * moment after it does and make it taller: a stack that was at its end stays there, so the
378
+ * newest toast stays in view. One the reader has scrolled back stays where they put it.
379
+ */
380
+ let endAt = 0;
381
+
382
+ /** How many toasts were shown at the last layout: a new one is scrolled to. */
383
+ let shownCount = 0;
384
+
385
+ $effect(() => {
386
+ const count = $toastStore.length;
387
+ layout();
388
+ const stack = region;
389
+ // The newest is at the end: in a stack that scrolls, that is what to show.
390
+ if (stack && count > shownCount && stack.style.overflowY === "auto") {
391
+ stack.scrollTop = stack.scrollHeight;
392
+ endAt = stack.scrollHeight - stack.clientHeight;
393
+ }
394
+ shownCount = count;
395
+ });
396
+
397
+ $effect(() => {
398
+ const stack = region;
399
+ if (!stack) return;
400
+ const again = () => layout();
401
+ const stopFooters = watchOverlayFooters(again);
402
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(again) : undefined;
403
+ observer?.observe(stack);
404
+ // Its children too: a toast that opens its details makes the stack taller inside a fixed height.
405
+ const children = new MutationObserver(() => {
406
+ observer?.disconnect();
407
+ observer?.observe(stack);
408
+ for (const child of stack.children) observer?.observe(child);
409
+ });
410
+ children.observe(stack, { childList: true });
411
+ // A named element moves with the page: follow it, once a frame.
412
+ let frame = 0;
413
+ const onScroll = () => {
414
+ if (frame || stack.childElementCount === 0) return;
415
+ frame = requestAnimationFrame(() => {
416
+ frame = 0;
417
+ layout();
418
+ });
419
+ };
420
+ document.addEventListener("scroll", onScroll, { capture: true, passive: true });
421
+ window.addEventListener("resize", again);
422
+ window.visualViewport?.addEventListener("resize", again);
423
+ window.visualViewport?.addEventListener("scroll", again);
424
+ return () => {
425
+ document.removeEventListener("scroll", onScroll, { capture: true });
426
+ if (frame) cancelAnimationFrame(frame);
427
+ window.visualViewport?.removeEventListener("scroll", again);
428
+ stopFooters();
429
+ observer?.disconnect();
430
+ children.disconnect();
431
+ window.removeEventListener("resize", again);
432
+ window.visualViewport?.removeEventListener("resize", again);
433
+ };
434
+ });
435
+
436
+ /**
437
+ * Escape in a toast gives focus back to where it came from (a control in
438
+ * the page, or in the modal that is open), and nothing else: the toast
439
+ * stays, and the modal under it does not take the key as its own.
440
+ */
441
+ function handleKeydown(event: KeyboardEvent) {
442
+ if (event.key !== "Escape" || event.defaultPrevented) return;
443
+ const back = returnTarget();
444
+ if (!back) return;
445
+ event.preventDefault();
446
+ back.focus();
447
+ }
448
+
449
+ /** What had focus before it entered the region; where it goes back to. */
450
+ let cameFrom: HTMLElement | null = null;
451
+
452
+ /**
453
+ * Where focus goes back to, if that is still a place for it. A control of
454
+ * the page that focus came from before a modal overlay opened is behind
455
+ * that overlay now: focus is not sent there, out of the overlay's trap.
456
+ */
457
+ function returnTarget(): HTMLElement | null {
458
+ if (!cameFrom?.isConnected) return null;
459
+ const modal = '[aria-modal="true"]';
460
+ if (document.querySelector(modal) && !cameFrom.closest(modal)) return null;
461
+ return cameFrom;
462
+ }
463
+
464
+ function handleFocusIn(event: FocusEvent) {
465
+ const from = event.relatedTarget;
466
+ if (from instanceof HTMLElement && !region?.contains(from)) cameFrom = from;
467
+ }
468
+
469
+ /**
470
+ * Dismissing the toast that holds focus (its action, or Dismiss) removes
471
+ * the focused control, and the browser would drop focus on `<body>`. It
472
+ * goes to the neighbouring toast instead, or back to where it came from.
473
+ * Which toast held focus has to be read before the list is re-rendered.
474
+ */
475
+ let shownIds: string[] = [];
476
+ let focusAfterDismiss: { neighbours: string[] } | null = null;
477
+
478
+ $effect.pre(() => {
479
+ const ids = $toastStore.map((toast) => toast.id);
480
+ const holder = region
481
+ ? (document.activeElement?.closest<HTMLElement>('[data-toast-id]') ?? null)
482
+ : null;
483
+ const held = holder && region?.contains(holder) ? holder.dataset.toastId : undefined;
484
+ if (held !== undefined && !ids.includes(held)) {
485
+ const at = shownIds.indexOf(held);
486
+ // The next toast first, then the ones before it, nearest first.
487
+ focusAfterDismiss = {
488
+ neighbours: [...shownIds.slice(at + 1), ...shownIds.slice(0, at).reverse()],
489
+ };
490
+ }
491
+ shownIds = ids;
492
+ });
493
+
494
+ $effect(() => {
495
+ void $toastStore;
496
+ const pending = focusAfterDismiss;
497
+ focusAfterDismiss = null;
498
+ if (!pending || !region) return;
499
+ // Something else took focus in the meantime (the action opened a dialog): leave it.
500
+ const active = document.activeElement;
501
+ if (active && active !== document.body && !region.contains(active)) return;
502
+
503
+ const toasts = [...region.querySelectorAll<HTMLElement>('[data-toast-id]')];
504
+ for (const id of pending.neighbours) {
505
+ const next = toasts
506
+ .find((toast) => toast.dataset.toastId === id)
507
+ ?.querySelector<HTMLElement>('button');
508
+ if (next) {
509
+ next.focus();
510
+ return;
511
+ }
512
+ }
513
+ returnTarget()?.focus();
514
+ });
11
515
  </script>
12
516
 
13
517
  <div
14
- class={cn("pointer-events-none fixed bottom-4 right-4 z-toast flex min-w-0 w-[min(24rem,calc(100vw-2rem))] flex-col gap-2 p-4", className)}
518
+ {...restProps}
519
+ class={cn("pointer-events-none fixed z-toast flex min-w-0 flex-col gap-2 overscroll-contain", placement, clipClass, className)}
15
520
  role="region"
16
- aria-label="Notifications"
521
+ aria-label={ariaLabel ?? words.regionLabel}
522
+ data-zabi-toaster
523
+ bind:this={region}
524
+ onfocusin={handleFocusIn}
525
+ onkeydown={handleKeydown}
17
526
  >
18
527
  <!-- Each toast owns its role="status"/"alert"; a region-level aria-live would announce twice. -->
19
528
  {#each $toastStore as toast (toast.id)}
20
- <ToasterToast {toast} />
529
+ <ToasterToast {toast} strings={words} {showCountdown} {defaultDuration} {onpausechange} {snackbar} {swipeToDismiss} {timeLine} />
21
530
  {/each}
22
531
  </div>