zabi-components 8.1.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 (273) hide show
  1. package/CHANGELOG.md +1011 -0
  2. package/README.md +1 -1
  3. package/THEME.md +18 -5
  4. package/THEMING.md +308 -74
  5. package/dist/atoms/ActionPanel.svelte +1 -1
  6. package/dist/atoms/Avatar.svelte +19 -3
  7. package/dist/atoms/Avatar.svelte.d.ts +12 -0
  8. package/dist/atoms/Badge.svelte +37 -8
  9. package/dist/atoms/Badge.svelte.d.ts +6 -0
  10. package/dist/atoms/Block.svelte +74 -0
  11. package/dist/atoms/Block.svelte.d.ts +29 -0
  12. package/dist/atoms/Button.svelte +52 -15
  13. package/dist/atoms/Button.svelte.d.ts +3 -2
  14. package/dist/atoms/Card.svelte +181 -25
  15. package/dist/atoms/Card.svelte.d.ts +55 -3
  16. package/dist/atoms/Checkbox.svelte +33 -0
  17. package/dist/atoms/Checkbox.svelte.d.ts +27 -0
  18. package/dist/atoms/Chip.svelte +351 -0
  19. package/dist/atoms/Chip.svelte.d.ts +57 -0
  20. package/dist/atoms/CodeField.svelte +385 -0
  21. package/dist/atoms/CodeField.svelte.d.ts +61 -0
  22. package/dist/atoms/ColorPicker.svelte +1 -0
  23. package/dist/atoms/DateField.svelte +41 -3
  24. package/dist/atoms/DateField.svelte.d.ts +31 -3
  25. package/dist/atoms/FloatingActionButton.svelte +5 -3
  26. package/dist/atoms/FloatingActionButton.svelte.d.ts +1 -1
  27. package/dist/atoms/Heading.svelte +27 -1
  28. package/dist/atoms/Heading.svelte.d.ts +7 -0
  29. package/dist/atoms/IconButton.svelte +81 -9
  30. package/dist/atoms/IconButton.svelte.d.ts +13 -0
  31. package/dist/atoms/Input.svelte +10 -5
  32. package/dist/atoms/Input.svelte.d.ts +3 -2
  33. package/dist/atoms/List.svelte +3 -2
  34. package/dist/atoms/ListItem.svelte +31 -7
  35. package/dist/atoms/ListItem.svelte.d.ts +6 -0
  36. package/dist/atoms/PickerField.svelte +239 -0
  37. package/dist/atoms/PickerField.svelte.d.ts +67 -0
  38. package/dist/atoms/Progress.svelte +78 -17
  39. package/dist/atoms/Progress.svelte.d.ts +11 -1
  40. package/dist/atoms/Radio.svelte +32 -0
  41. package/dist/atoms/Radio.svelte.d.ts +26 -0
  42. package/dist/atoms/Rating.svelte +132 -6
  43. package/dist/atoms/Rating.svelte.d.ts +24 -3
  44. package/dist/atoms/Select.svelte +24 -26
  45. package/dist/atoms/Select.svelte.d.ts +12 -0
  46. package/dist/atoms/SelectionControl.svelte +99 -6
  47. package/dist/atoms/SelectionControl.svelte.d.ts +25 -0
  48. package/dist/atoms/Skeleton.svelte +114 -18
  49. package/dist/atoms/Skeleton.svelte.d.ts +30 -3
  50. package/dist/atoms/SkeletonGroup.svelte +43 -0
  51. package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
  52. package/dist/atoms/Slider.svelte +35 -0
  53. package/dist/atoms/Sparkline.svelte +109 -0
  54. package/dist/atoms/Sparkline.svelte.d.ts +27 -0
  55. package/dist/atoms/SpeechBubble.svelte +187 -0
  56. package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
  57. package/dist/atoms/Stat.svelte +72 -0
  58. package/dist/atoms/Stat.svelte.d.ts +19 -0
  59. package/dist/atoms/TemporalField.svelte +342 -10
  60. package/dist/atoms/TemporalField.svelte.d.ts +20 -4
  61. package/dist/atoms/Textarea.svelte +100 -5
  62. package/dist/atoms/Textarea.svelte.d.ts +21 -0
  63. package/dist/atoms/TimeColumn.svelte +150 -0
  64. package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
  65. package/dist/atoms/TimeField.svelte +46 -3
  66. package/dist/atoms/TimeField.svelte.d.ts +34 -3
  67. package/dist/atoms/Toast.svelte +6 -5
  68. package/dist/atoms/Toggle.svelte +61 -10
  69. package/dist/atoms/Toggle.svelte.d.ts +15 -0
  70. package/dist/atoms/Tooltip.svelte +20 -1
  71. package/dist/atoms/Tooltip.svelte.d.ts +7 -0
  72. package/dist/atoms/index.d.ts +21 -0
  73. package/dist/atoms/index.js +17 -0
  74. package/dist/atoms/selection-control.styles.d.ts +15 -4
  75. package/dist/atoms/selection-control.styles.js +16 -5
  76. package/dist/components/atoms/index.d.ts +21 -0
  77. package/dist/components/atoms/index.d.ts.map +1 -1
  78. package/dist/components/atoms/selection-control.styles.d.ts +15 -4
  79. package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
  80. package/dist/components/index.d.ts +27 -3
  81. package/dist/components/index.d.ts.map +1 -1
  82. package/dist/components/map.d.ts +11 -0
  83. package/dist/components/map.d.ts.map +1 -0
  84. package/dist/components/molecules/index.d.ts +7 -2
  85. package/dist/components/molecules/index.d.ts.map +1 -1
  86. package/dist/components/molecules/toast-store.d.ts +30 -0
  87. package/dist/components/molecules/toast-store.d.ts.map +1 -1
  88. package/dist/components/organisms/index.d.ts +6 -1
  89. package/dist/components/organisms/index.d.ts.map +1 -1
  90. package/dist/components/types/variants.d.ts +12 -3
  91. package/dist/components/types/variants.d.ts.map +1 -1
  92. package/dist/components/util/app-shell.d.ts +55 -4
  93. package/dist/components/util/app-shell.d.ts.map +1 -1
  94. package/dist/components/util/avatar.d.ts +3 -0
  95. package/dist/components/util/avatar.d.ts.map +1 -1
  96. package/dist/components/util/bottom-tab-bar.d.ts +6 -0
  97. package/dist/components/util/bottom-tab-bar.d.ts.map +1 -1
  98. package/dist/components/util/chart.d.ts +140 -0
  99. package/dist/components/util/chart.d.ts.map +1 -0
  100. package/dist/components/util/chip.d.ts +66 -0
  101. package/dist/components/util/chip.d.ts.map +1 -0
  102. package/dist/components/util/cn.d.ts.map +1 -1
  103. package/dist/components/util/code-field.d.ts +63 -0
  104. package/dist/components/util/code-field.d.ts.map +1 -0
  105. package/dist/components/util/collapsible.d.ts +6 -0
  106. package/dist/components/util/collapsible.d.ts.map +1 -1
  107. package/dist/components/util/display-voice.d.ts +10 -0
  108. package/dist/components/util/display-voice.d.ts.map +1 -0
  109. package/dist/components/util/field-control.d.ts +30 -0
  110. package/dist/components/util/field-control.d.ts.map +1 -0
  111. package/dist/components/util/field.d.ts +6 -0
  112. package/dist/components/util/field.d.ts.map +1 -1
  113. package/dist/components/util/focus-utils.d.ts +2 -0
  114. package/dist/components/util/focus-utils.d.ts.map +1 -1
  115. package/dist/components/util/map-view.d.ts +144 -0
  116. package/dist/components/util/map-view.d.ts.map +1 -0
  117. package/dist/components/util/photo.d.ts +19 -0
  118. package/dist/components/util/photo.d.ts.map +1 -1
  119. package/dist/components/util/progress.d.ts +19 -0
  120. package/dist/components/util/progress.d.ts.map +1 -0
  121. package/dist/components/util/scroll-edge.d.ts +33 -0
  122. package/dist/components/util/scroll-edge.d.ts.map +1 -0
  123. package/dist/components/util/sheet-drag.d.ts +25 -1
  124. package/dist/components/util/sheet-drag.d.ts.map +1 -1
  125. package/dist/components/util/skeleton.d.ts +15 -0
  126. package/dist/components/util/skeleton.d.ts.map +1 -0
  127. package/dist/components/util/sliding-indicator.d.ts +25 -0
  128. package/dist/components/util/sliding-indicator.d.ts.map +1 -0
  129. package/dist/components/util/temporal-field.d.ts +89 -0
  130. package/dist/components/util/temporal-field.d.ts.map +1 -0
  131. package/dist/components/util/toaster.d.ts +40 -0
  132. package/dist/components/util/toaster.d.ts.map +1 -1
  133. package/dist/components/util/tone-surface.d.ts +34 -0
  134. package/dist/components/util/tone-surface.d.ts.map +1 -0
  135. package/dist/components/util/top-navbar.d.ts +26 -2
  136. package/dist/components/util/top-navbar.d.ts.map +1 -1
  137. package/dist/components/util/zabi-strings.d.ts +9 -0
  138. package/dist/components/util/zabi-strings.d.ts.map +1 -1
  139. package/dist/create-theme/cli.js +6 -2
  140. package/dist/create-theme/index.d.ts +17 -3
  141. package/dist/create-theme/index.js +252 -35
  142. package/dist/create-theme/lib/resolve.js +97 -0
  143. package/dist/create-theme/theme-data.js +1618 -90
  144. package/dist/index.d.ts +27 -3
  145. package/dist/index.js +18 -0
  146. package/dist/map.d.ts +10 -0
  147. package/dist/map.js +9 -0
  148. package/dist/molecules/Alert.svelte +31 -12
  149. package/dist/molecules/Alert.svelte.d.ts +12 -1
  150. package/dist/molecules/AppBar.svelte +287 -28
  151. package/dist/molecules/AppBar.svelte.d.ts +62 -0
  152. package/dist/molecules/AvatarGroup.svelte +1 -1
  153. package/dist/molecules/BarChart.svelte +381 -0
  154. package/dist/molecules/BarChart.svelte.d.ts +45 -0
  155. package/dist/molecules/BottomSheet.svelte +88 -20
  156. package/dist/molecules/BottomSheet.svelte.d.ts +17 -1
  157. package/dist/molecules/BottomTabBar.svelte +134 -28
  158. package/dist/molecules/BottomTabBar.svelte.d.ts +36 -2
  159. package/dist/molecules/Calendar.svelte.d.ts +1 -1
  160. package/dist/molecules/ChipGroup.svelte +325 -0
  161. package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
  162. package/dist/molecules/Collapsible.svelte +158 -54
  163. package/dist/molecules/Collapsible.svelte.d.ts +23 -6
  164. package/dist/molecules/CollapsibleGroup.svelte +16 -6
  165. package/dist/molecules/ConfirmDialog.svelte +5 -1
  166. package/dist/molecules/ConfirmDialog.svelte.d.ts +3 -0
  167. package/dist/molecules/Drawer.svelte +33 -12
  168. package/dist/molecules/Dropdown.svelte +9 -1
  169. package/dist/molecules/Dropdown.svelte.d.ts +2 -0
  170. package/dist/molecules/DropdownItem.svelte +1 -1
  171. package/dist/molecules/ImageUpload.svelte +230 -120
  172. package/dist/molecules/ImageUpload.svelte.d.ts +38 -3
  173. package/dist/molecules/LineChart.svelte +459 -0
  174. package/dist/molecules/LineChart.svelte.d.ts +53 -0
  175. package/dist/molecules/MapView.svelte +565 -0
  176. package/dist/molecules/MapView.svelte.d.ts +88 -0
  177. package/dist/molecules/Modal.svelte +11 -6
  178. package/dist/molecules/PhotoGrid.svelte +100 -10
  179. package/dist/molecules/PhotoGrid.svelte.d.ts +11 -0
  180. package/dist/molecules/PhotoViewer.svelte +121 -24
  181. package/dist/molecules/PhotoViewer.svelte.d.ts +18 -0
  182. package/dist/molecules/PieChart.svelte +421 -0
  183. package/dist/molecules/PieChart.svelte.d.ts +51 -0
  184. package/dist/molecules/RadioGroup.svelte +20 -4
  185. package/dist/molecules/RadioGroup.svelte.d.ts +5 -0
  186. package/dist/molecules/SegmentedControl.svelte +136 -3
  187. package/dist/molecules/SegmentedControl.svelte.d.ts +6 -0
  188. package/dist/molecules/SidebarFooter.svelte +1 -1
  189. package/dist/molecules/SidebarNavSection.svelte +1 -1
  190. package/dist/molecules/SortableList.svelte +1 -0
  191. package/dist/molecules/Stepper.svelte +21 -7
  192. package/dist/molecules/StickyActionBar.svelte +67 -8
  193. package/dist/molecules/StickyActionBar.svelte.d.ts +14 -1
  194. package/dist/molecules/Tabs.svelte +79 -2
  195. package/dist/molecules/Toaster.svelte +171 -7
  196. package/dist/molecules/Toaster.svelte.d.ts +60 -2
  197. package/dist/molecules/ToasterToast.svelte +292 -60
  198. package/dist/molecules/ToasterToast.svelte.d.ts +6 -0
  199. package/dist/molecules/UnsavedChangesBar.svelte +13 -2
  200. package/dist/molecules/index.d.ts +7 -2
  201. package/dist/molecules/index.js +4 -0
  202. package/dist/molecules/toast-store.d.ts +30 -0
  203. package/dist/molecules/toast-store.js +4 -1
  204. package/dist/organisms/AppNavigation.svelte +440 -0
  205. package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
  206. package/dist/organisms/AppShell.svelte +539 -39
  207. package/dist/organisms/AppShell.svelte.d.ts +102 -6
  208. package/dist/organisms/AppShellHeaderPart.svelte +163 -0
  209. package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
  210. package/dist/organisms/SidebarNavigation.svelte +23 -36
  211. package/dist/organisms/SidebarPanel.svelte +10 -14
  212. package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
  213. package/dist/organisms/SidebarShell.svelte +2 -3
  214. package/dist/organisms/TopNavbar.svelte +117 -28
  215. package/dist/organisms/TopNavbar.svelte.d.ts +22 -1
  216. package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
  217. package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
  218. package/dist/organisms/index.d.ts +6 -1
  219. package/dist/organisms/index.js +4 -0
  220. package/dist/types/components.d.ts +17 -1
  221. package/dist/types/components.d.ts.map +1 -1
  222. package/dist/types/index.d.ts +497 -10
  223. package/dist/types/index.d.ts.map +1 -1
  224. package/dist/types/variants.ts +14 -3
  225. package/dist/util/app-shell.d.ts +55 -4
  226. package/dist/util/app-shell.js +32 -7
  227. package/dist/util/avatar.d.ts +3 -0
  228. package/dist/util/bottom-tab-bar.d.ts +6 -0
  229. package/dist/util/chart.d.ts +139 -0
  230. package/dist/util/chart.js +305 -0
  231. package/dist/util/chip.d.ts +65 -0
  232. package/dist/util/chip.js +58 -0
  233. package/dist/util/cn.js +28 -2
  234. package/dist/util/code-field.d.ts +62 -0
  235. package/dist/util/code-field.js +112 -0
  236. package/dist/util/collapsible.d.ts +6 -0
  237. package/dist/util/display-voice.d.ts +9 -0
  238. package/dist/util/display-voice.js +9 -0
  239. package/dist/util/field-control.d.ts +29 -0
  240. package/dist/util/field-control.js +32 -0
  241. package/dist/util/field.d.ts +6 -0
  242. package/dist/util/focus-utils.d.ts +2 -0
  243. package/dist/util/focus-utils.js +4 -0
  244. package/dist/util/map-view.d.ts +143 -0
  245. package/dist/util/map-view.js +121 -0
  246. package/dist/util/photo.d.ts +19 -0
  247. package/dist/util/photo.js +3 -0
  248. package/dist/util/progress.d.ts +18 -0
  249. package/dist/util/progress.js +17 -0
  250. package/dist/util/scroll-edge.d.ts +32 -0
  251. package/dist/util/scroll-edge.js +87 -0
  252. package/dist/util/sheet-drag.d.ts +25 -1
  253. package/dist/util/sheet-drag.js +71 -1
  254. package/dist/util/skeleton.d.ts +14 -0
  255. package/dist/util/skeleton.js +22 -0
  256. package/dist/util/sliding-indicator.d.ts +24 -0
  257. package/dist/util/sliding-indicator.js +44 -0
  258. package/dist/util/temporal-field.d.ts +88 -0
  259. package/dist/util/temporal-field.js +136 -0
  260. package/dist/util/toaster.d.ts +40 -0
  261. package/dist/util/toaster.js +55 -0
  262. package/dist/util/tone-surface.d.ts +33 -0
  263. package/dist/util/tone-surface.js +35 -0
  264. package/dist/util/top-navbar.d.ts +26 -2
  265. package/dist/util/top-navbar.js +23 -0
  266. package/dist/util/zabi-strings.d.ts +9 -0
  267. package/dist/zabi-components-colors.css +9 -3
  268. package/dist/zabi-components-theme-dark-only.css +474 -164
  269. package/dist/zabi-components-theme-dark.css +474 -164
  270. package/dist/zabi-components-theme-only.css +933 -99
  271. package/dist/zabi-components-theme.css +933 -99
  272. package/dist/zabi-components.css +2275 -321
  273. package/package.json +11 -1
@@ -8,7 +8,10 @@
8
8
  import { topOverlayFooter, topOverlayHeader, watchOverlayFooters } from "../util/overlay.js";
9
9
  import {
10
10
  DEFAULT_TOASTER_STRINGS,
11
+ toastsReplaced,
12
+ toastStays,
11
13
  type ToastDuration,
14
+ type ToasterMode,
12
15
  type ToasterStrings,
13
16
  type ToastPauseChange,
14
17
  } from "../util/toaster.js";
@@ -28,7 +31,7 @@
28
31
  * `--toaster-bottom-offset` on the Toaster or any ancestor:
29
32
  *
30
33
  * ```svelte
31
- * <Toaster style="--toaster-bottom-offset: 65px" />
34
+ * <Toaster style="--toaster-bottom-offset: 64px" />
32
35
  * ```
33
36
  *
34
37
  * A `FloatingActionButton` is 56px with 16px under it: `72px` puts the
@@ -63,6 +66,29 @@
63
66
  * own time (WCAG 2.2.1). A finger that lifts leaves it at least three
64
67
  * seconds, so holding a short toast starts it again.
65
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
+ *
66
92
  * Every word the toaster says by itself is in `strings`:
67
93
  *
68
94
  * ```svelte
@@ -100,6 +126,41 @@
100
126
  * on the toast wins.
101
127
  */
102
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;
103
164
  /**
104
165
  * Called when a pointer, a finger or focus starts or stops holding a
105
166
  * toast's timer, with the toast's id. The same state is `data-paused`
@@ -114,6 +175,11 @@
114
175
  "aria-label": ariaLabel,
115
176
  showCountdown = false,
116
177
  defaultDuration,
178
+ mode = "stack",
179
+ max,
180
+ avoid,
181
+ swipeToDismiss = true,
182
+ timeLine,
117
183
  onpausechange,
118
184
  class: className = '',
119
185
  ...restProps
@@ -143,8 +209,77 @@
143
209
 
144
210
  let region = $state<HTMLDivElement>();
145
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
+
146
253
  /** The highest the stack may reach: below the status bar or an AppShell's top bar. */
147
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;
281
+ }
282
+
148
283
  /**
149
284
  * Places the stack for what is on screen now. At rest it is where the
150
285
  * classes put it, raised above the on-screen keyboard where that covers
@@ -175,10 +310,17 @@
175
310
  const keyboard = readKeyboardInset();
176
311
  const raise = (px: number) => stack.style.setProperty("--toaster-overlay-inset", `${px}px`);
177
312
  if (keyboard > 0) raise(keyboard);
178
- const rest = stack.getBoundingClientRect();
313
+ let rest = stack.getBoundingClientRect();
179
314
  // No layout (a test DOM): nothing to place.
180
315
  if (rest.width === 0 && rest.height === 0) return;
181
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
+
182
324
  stack.style.top = TOP;
183
325
  stack.style.bottom = "auto";
184
326
  const topLimit = stack.getBoundingClientRect().top;
@@ -222,12 +364,21 @@
222
364
  // over the header after all.
223
365
  const padding = parseFloat(getComputedStyle(stack).paddingTop) || 0;
224
366
  if (room < 2 * padding + 44) stack.style.paddingBlock = "0px";
225
- // Where it was, or at its end when it has only now begun to scroll:
226
- // the newest toast is the last one.
227
- stack.scrollTop = scrolledTo ?? stack.scrollHeight;
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;
228
372
  }
229
373
  }
230
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
+
231
382
  /** How many toasts were shown at the last layout: a new one is scrolled to. */
232
383
  let shownCount = 0;
233
384
 
@@ -238,6 +389,7 @@
238
389
  // The newest is at the end: in a stack that scrolls, that is what to show.
239
390
  if (stack && count > shownCount && stack.style.overflowY === "auto") {
240
391
  stack.scrollTop = stack.scrollHeight;
392
+ endAt = stack.scrollHeight - stack.clientHeight;
241
393
  }
242
394
  shownCount = count;
243
395
  });
@@ -256,10 +408,22 @@
256
408
  for (const child of stack.children) observer?.observe(child);
257
409
  });
258
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 });
259
421
  window.addEventListener("resize", again);
260
422
  window.visualViewport?.addEventListener("resize", again);
261
423
  window.visualViewport?.addEventListener("scroll", again);
262
424
  return () => {
425
+ document.removeEventListener("scroll", onScroll, { capture: true });
426
+ if (frame) cancelAnimationFrame(frame);
263
427
  window.visualViewport?.removeEventListener("scroll", again);
264
428
  stopFooters();
265
429
  observer?.disconnect();
@@ -352,7 +516,7 @@
352
516
 
353
517
  <div
354
518
  {...restProps}
355
- class={cn("pointer-events-none fixed z-toast flex min-w-0 flex-col gap-2 overscroll-contain", placement, className)}
519
+ class={cn("pointer-events-none fixed z-toast flex min-w-0 flex-col gap-2 overscroll-contain", placement, clipClass, className)}
356
520
  role="region"
357
521
  aria-label={ariaLabel ?? words.regionLabel}
358
522
  data-zabi-toaster
@@ -362,6 +526,6 @@
362
526
  >
363
527
  <!-- Each toast owns its role="status"/"alert"; a region-level aria-live would announce twice. -->
364
528
  {#each $toastStore as toast (toast.id)}
365
- <ToasterToast {toast} strings={words} {showCountdown} {defaultDuration} {onpausechange} />
529
+ <ToasterToast {toast} strings={words} {showCountdown} {defaultDuration} {onpausechange} {snackbar} {swipeToDismiss} {timeLine} />
366
530
  {/each}
367
531
  </div>
@@ -1,4 +1,4 @@
1
- import { type ToastDuration, type ToasterStrings, type ToastPauseChange } from "../util/toaster.js";
1
+ import { type ToastDuration, type ToasterMode, type ToasterStrings, type ToastPauseChange } from "../util/toaster.js";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  /**
4
4
  * The stack of toasts, fixed to the bottom of the screen: in the corner
@@ -14,7 +14,7 @@ import type { HTMLAttributes } from "svelte/elements";
14
14
  * `--toaster-bottom-offset` on the Toaster or any ancestor:
15
15
  *
16
16
  * ```svelte
17
- * <Toaster style="--toaster-bottom-offset: 65px" />
17
+ * <Toaster style="--toaster-bottom-offset: 64px" />
18
18
  * ```
19
19
  *
20
20
  * A `FloatingActionButton` is 56px with 16px under it: `72px` puts the
@@ -49,6 +49,29 @@ import type { HTMLAttributes } from "svelte/elements";
49
49
  * own time (WCAG 2.2.1). A finger that lifts leaves it at least three
50
50
  * seconds, so holding a short toast starts it again.
51
51
  * `data-paused` on the toast says so, and `onpausechange` is called.
52
+ * A mouse holds the timer once it has moved over the toast, not when the
53
+ * toast comes up under a pointer that is at rest. A touch or a pen drags
54
+ * a toast away sideways (`swipeToDismiss`, on by default), and the close
55
+ * button stays as the way for a keyboard and a screen reader. A toast
56
+ * pushed with the id of one that is showing replaces it in place, and
57
+ * `max` limits how many show at once: the oldest toast that is timed
58
+ * (not an error, not one with an action) is the one that leaves.
59
+ *
60
+ * For a phone, `mode="auto"` makes the toast a snackbar below 640px: one
61
+ * row (two lines at most), one at a time, with its action beside its text
62
+ * and no stack. It sits directly above what is docked at the bottom, which
63
+ * it finds by itself: a `BottomTabBar`, a `StickyActionBar`, a
64
+ * `FloatingActionButton` and an `UnsavedChangesBar` that lie under it, and
65
+ * anything you name in `avoid` (the button a screen ends in). It comes out
66
+ * from behind that edge and goes back into it, on `--ease-standard`, with
67
+ * no movement under reduced motion, and lies over the page without moving
68
+ * it. Push a short confirmation with `closable: false` and it has no
69
+ * control and takes no taps.
70
+ *
71
+ * ```svelte
72
+ * <Toaster mode="auto" avoid="[data-primary-action]" />
73
+ * ```
74
+ *
52
75
  * Every word the toaster says by itself is in `strings`:
53
76
  *
54
77
  * ```svelte
@@ -86,6 +109,41 @@ type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-label
86
109
  * on the toast wins.
87
110
  */
88
111
  defaultDuration?: ToastDuration;
112
+ /**
113
+ * `"stack"` (default): the stack of toasts. `"snackbar"`: the compact
114
+ * form for a phone, at every width. `"auto"`: the snackbar below
115
+ * 640px, the stack from there up.
116
+ */
117
+ mode?: ToasterMode;
118
+ /**
119
+ * How many toasts show at once. The oldest one that is timed leaves
120
+ * when a newer one arrives; a toast that has to stay (an error, one
121
+ * with an action, `"persistent"`) is not dropped, so there can be more
122
+ * than this while they are showing. Default: no limit, and 1 in the
123
+ * snackbar.
124
+ */
125
+ max?: number;
126
+ /**
127
+ * A CSS selector for elements the stack must not lie on, in addition
128
+ * to the docked bars it finds by itself: the button a screen ends in.
129
+ * When one lies under the stack, the stack sits above it.
130
+ */
131
+ avoid?: string;
132
+ /**
133
+ * Draws the thin bar along the bottom of a toast that empties over its
134
+ * time. Unset: drawn, except under a confirmation in the snackbar that
135
+ * has nothing to press (pushed with `closable: false`), where it is
136
+ * left out. `false` leaves it out everywhere, `true` draws it
137
+ * everywhere; a toast's own `timeLine` wins. The sentence for a screen
138
+ * reader about the time left stays.
139
+ */
140
+ timeLine?: boolean;
141
+ /**
142
+ * Touch and pen drag a toast away sideways (and down, in the
143
+ * snackbar). Default `true`. A mouse does not drag; the close button
144
+ * is the way for a keyboard and a screen reader.
145
+ */
146
+ swipeToDismiss?: boolean;
89
147
  /**
90
148
  * Called when a pointer, a finger or focus starts or stops holding a
91
149
  * toast's timer, with the toast's id. The same state is `data-paused`