@bitforgehq/ui 0.3.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 (281) hide show
  1. package/CHANGELOG.md +3665 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/ai/MANIFEST.index.json +383 -0
  5. package/ai/MANIFEST.json +7528 -0
  6. package/ai/REFERENCE.index.json +1461 -0
  7. package/ai/REFERENCE.json +7213 -0
  8. package/ai/UI_GUIDE.md +1159 -0
  9. package/ai/components/action-bar.md +209 -0
  10. package/ai/components/action-sheet.md +526 -0
  11. package/ai/components/alert-dialog.md +509 -0
  12. package/ai/components/alert.md +376 -0
  13. package/ai/components/avatar.md +146 -0
  14. package/ai/components/back-button.md +286 -0
  15. package/ai/components/badge.md +171 -0
  16. package/ai/components/button.md +431 -0
  17. package/ai/components/card.md +303 -0
  18. package/ai/components/checkbox.md +418 -0
  19. package/ai/components/chip-group.md +205 -0
  20. package/ai/components/chip.md +300 -0
  21. package/ai/components/content.md +474 -0
  22. package/ai/components/desktop-toast.md +420 -0
  23. package/ai/components/dialog.md +962 -0
  24. package/ai/components/empty-state.md +146 -0
  25. package/ai/components/field.md +319 -0
  26. package/ai/components/header.md +809 -0
  27. package/ai/components/icon-button.md +221 -0
  28. package/ai/components/icon.md +176 -0
  29. package/ai/components/input.md +455 -0
  30. package/ai/components/list-item.md +326 -0
  31. package/ai/components/list.md +178 -0
  32. package/ai/components/menu-button.md +377 -0
  33. package/ai/components/menu.md +661 -0
  34. package/ai/components/modal.md +613 -0
  35. package/ai/components/number-input.md +423 -0
  36. package/ai/components/page.md +299 -0
  37. package/ai/components/progress.md +215 -0
  38. package/ai/components/radio-group.md +432 -0
  39. package/ai/components/refresher.md +377 -0
  40. package/ai/components/segmented.md +517 -0
  41. package/ai/components/select.md +510 -0
  42. package/ai/components/sheet.md +845 -0
  43. package/ai/components/spinner.md +171 -0
  44. package/ai/components/stack-outlet.md +661 -0
  45. package/ai/components/tab-bar.md +427 -0
  46. package/ai/components/tab.md +295 -0
  47. package/ai/components/tabs.md +408 -0
  48. package/ai/components/textarea.md +371 -0
  49. package/ai/components/toast.md +522 -0
  50. package/ai/components/toggle.md +387 -0
  51. package/ai/reference/bf-action-directive.md +21 -0
  52. package/ai/reference/bf-action-sheet-action-role.md +27 -0
  53. package/ai/reference/bf-action-sheet-action.md +28 -0
  54. package/ai/reference/bf-action-sheet-options.md +28 -0
  55. package/ai/reference/bf-action-sheet-service.md +33 -0
  56. package/ai/reference/bf-alert-dialog-button-role.md +27 -0
  57. package/ai/reference/bf-alert-dialog-button.md +27 -0
  58. package/ai/reference/bf-alert-dialog-options.md +29 -0
  59. package/ai/reference/bf-alert-dialog-service.md +34 -0
  60. package/ai/reference/bf-alert-variant.md +28 -0
  61. package/ai/reference/bf-autocapitalize.md +30 -0
  62. package/ai/reference/bf-avatar-size.md +27 -0
  63. package/ai/reference/bf-avatar-variant.md +28 -0
  64. package/ai/reference/bf-back-at-root.md +28 -0
  65. package/ai/reference/bf-back-button-service.md +40 -0
  66. package/ai/reference/bf-badge-variant.md +29 -0
  67. package/ai/reference/bf-button-size.md +29 -0
  68. package/ai/reference/bf-button-type.md +28 -0
  69. package/ai/reference/bf-button-variant.md +29 -0
  70. package/ai/reference/bf-card-padding.md +26 -0
  71. package/ai/reference/bf-card-variant.md +28 -0
  72. package/ai/reference/bf-chip-group-mode.md +27 -0
  73. package/ai/reference/bf-chip-variant.md +26 -0
  74. package/ai/reference/bf-compare-with.md +24 -0
  75. package/ai/reference/bf-component-overlay-ref.md +33 -0
  76. package/ai/reference/bf-confirm-button-role.md +26 -0
  77. package/ai/reference/bf-confirm-button.md +27 -0
  78. package/ai/reference/bf-confirm-options.md +31 -0
  79. package/ai/reference/bf-confirm-size.md +26 -0
  80. package/ai/reference/bf-content-scroll-event.md +21 -0
  81. package/ai/reference/bf-defaults-options.md +32 -0
  82. package/ai/reference/bf-defaults.md +28 -0
  83. package/ai/reference/bf-desktop-toast-config.md +28 -0
  84. package/ai/reference/bf-desktop-toast-harness.md +33 -0
  85. package/ai/reference/bf-desktop-toast-options.md +28 -0
  86. package/ai/reference/bf-desktop-toast-position.md +28 -0
  87. package/ai/reference/bf-desktop-toast-service.md +42 -0
  88. package/ai/reference/bf-dialog-config.md +44 -0
  89. package/ai/reference/bf-dialog-harness.md +52 -0
  90. package/ai/reference/bf-dialog-ref.md +44 -0
  91. package/ai/reference/bf-dialog-role.md +26 -0
  92. package/ai/reference/bf-dialog-service.md +55 -0
  93. package/ai/reference/bf-dialog-size.md +31 -0
  94. package/ai/reference/bf-dialog-template-context.md +32 -0
  95. package/ai/reference/bf-empty-state-heading-level.md +26 -0
  96. package/ai/reference/bf-enter-key-hint.md +31 -0
  97. package/ai/reference/bf-field-control-class.md +32 -0
  98. package/ai/reference/bf-field-control-state.md +28 -0
  99. package/ai/reference/bf-field-size.md +29 -0
  100. package/ai/reference/bf-haptics-impact-style.md +27 -0
  101. package/ai/reference/bf-haptics-notification-type.md +27 -0
  102. package/ai/reference/bf-haptics-service.md +41 -0
  103. package/ai/reference/bf-harness-filters.md +29 -0
  104. package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
  105. package/ai/reference/bf-icon-button-variant.md +28 -0
  106. package/ai/reference/bf-icon-size.md +27 -0
  107. package/ai/reference/bf-input-autocapitalize.md +36 -0
  108. package/ai/reference/bf-input-enter-key-hint.md +38 -0
  109. package/ai/reference/bf-input-input-mode.md +38 -0
  110. package/ai/reference/bf-input-mode.md +32 -0
  111. package/ai/reference/bf-input-type.md +35 -0
  112. package/ai/reference/bf-jsdom-shim-options.md +22 -0
  113. package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
  114. package/ai/reference/bf-keyboard-assist-directive.md +185 -0
  115. package/ai/reference/bf-keyboard-phase.md +30 -0
  116. package/ai/reference/bf-keyboard-service.md +50 -0
  117. package/ai/reference/bf-labels-overrides.md +26 -0
  118. package/ai/reference/bf-labels.md +44 -0
  119. package/ai/reference/bf-menu-button-harness.md +40 -0
  120. package/ai/reference/bf-menu-button-variant.md +27 -0
  121. package/ai/reference/bf-menu-divider-directive.md +13 -0
  122. package/ai/reference/bf-menu-group-directive.md +14 -0
  123. package/ai/reference/bf-menu-harness.md +55 -0
  124. package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
  125. package/ai/reference/bf-menu-item-directive.md +31 -0
  126. package/ai/reference/bf-menu-item-harness.md +52 -0
  127. package/ai/reference/bf-menu-item-radio-directive.md +29 -0
  128. package/ai/reference/bf-menu-placement.md +29 -0
  129. package/ai/reference/bf-menu-trigger-directive.md +46 -0
  130. package/ai/reference/bf-modal-options.md +30 -0
  131. package/ai/reference/bf-modal-presentation.md +26 -0
  132. package/ai/reference/bf-modal-service.md +38 -0
  133. package/ai/reference/bf-nav-controller.md +59 -0
  134. package/ai/reference/bf-nav-direction.md +23 -0
  135. package/ai/reference/bf-nav-target.md +21 -0
  136. package/ai/reference/bf-number-input-mode.md +28 -0
  137. package/ai/reference/bf-number-input-step.md +25 -0
  138. package/ai/reference/bf-overlay-auto-focus.md +29 -0
  139. package/ai/reference/bf-overlay-controller.md +22 -0
  140. package/ai/reference/bf-overlay-host.md +36 -0
  141. package/ai/reference/bf-overlay-options.md +53 -0
  142. package/ai/reference/bf-overlay-position.md +30 -0
  143. package/ai/reference/bf-overlay-ref.md +53 -0
  144. package/ai/reference/bf-overlay-role.md +27 -0
  145. package/ai/reference/bf-overlay-service.md +53 -0
  146. package/ai/reference/bf-overlay-stack-options.md +24 -0
  147. package/ai/reference/bf-overlay-stack.md +81 -0
  148. package/ai/reference/bf-overlay-template-context.md +24 -0
  149. package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
  150. package/ai/reference/bf-page-context.md +24 -0
  151. package/ai/reference/bf-platform-service.md +53 -0
  152. package/ai/reference/bf-platform.md +28 -0
  153. package/ai/reference/bf-progress-size.md +26 -0
  154. package/ai/reference/bf-progress-tone.md +28 -0
  155. package/ai/reference/bf-radio-group-harness.md +49 -0
  156. package/ai/reference/bf-radio-group-orientation.md +26 -0
  157. package/ai/reference/bf-radio-option.md +30 -0
  158. package/ai/reference/bf-refresher-refresh-event.md +22 -0
  159. package/ai/reference/bf-segment-option.md +29 -0
  160. package/ai/reference/bf-segmented-harness.md +48 -0
  161. package/ai/reference/bf-segmented-size.md +29 -0
  162. package/ai/reference/bf-select-harness.md +70 -0
  163. package/ai/reference/bf-select-option.md +29 -0
  164. package/ai/reference/bf-sheet-auto-focus.md +29 -0
  165. package/ai/reference/bf-sheet-options.md +32 -0
  166. package/ai/reference/bf-sheet-ref.md +45 -0
  167. package/ai/reference/bf-sheet-service.md +34 -0
  168. package/ai/reference/bf-shell-config-mode.md +28 -0
  169. package/ai/reference/bf-shell-config.md +38 -0
  170. package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
  171. package/ai/reference/bf-shell-dynamic-type.md +25 -0
  172. package/ai/reference/bf-shell-mode.md +28 -0
  173. package/ai/reference/bf-shell-testing-options.md +26 -0
  174. package/ai/reference/bf-size.md +45 -0
  175. package/ai/reference/bf-spinner-size.md +27 -0
  176. package/ai/reference/bf-stack-change.md +22 -0
  177. package/ai/reference/bf-stack-reuse-strategy.md +32 -0
  178. package/ai/reference/bf-stack-transition-event.md +26 -0
  179. package/ai/reference/bf-stack-transition.md +26 -0
  180. package/ai/reference/bf-stacked-overlay.md +23 -0
  181. package/ai/reference/bf-system-bars-style.md +38 -0
  182. package/ai/reference/bf-tab-bar-controller.md +51 -0
  183. package/ai/reference/bf-tab-bar-host.md +52 -0
  184. package/ai/reference/bf-tab-context.md +32 -0
  185. package/ai/reference/bf-tab-links.md +37 -0
  186. package/ai/reference/bf-testing-options.md +27 -0
  187. package/ai/reference/bf-text-scale-service.md +35 -0
  188. package/ai/reference/bf-theme.md +27 -0
  189. package/ai/reference/bf-toast-action.md +25 -0
  190. package/ai/reference/bf-toast-controller.md +17 -0
  191. package/ai/reference/bf-toast-dismiss-reason.md +29 -0
  192. package/ai/reference/bf-toast-options.md +29 -0
  193. package/ai/reference/bf-toast-position.md +26 -0
  194. package/ai/reference/bf-toast-ref.md +41 -0
  195. package/ai/reference/bf-toast-service.md +39 -0
  196. package/ai/reference/bf-toggle-label-position.md +26 -0
  197. package/ai/reference/bf-transition-kind.md +28 -0
  198. package/ai/reference/bf-validation-message.md +31 -0
  199. package/ai/reference/bf-validation-messages.md +33 -0
  200. package/ai/reference/bf-view-event.md +28 -0
  201. package/ai/reference/bf-view-lifecycle.md +41 -0
  202. package/ai/reference/bf-view-state.md +28 -0
  203. package/ai/reference/bf_default_labels.md +21 -0
  204. package/ai/reference/bf_default_values.md +21 -0
  205. package/ai/reference/bf_defaults.md +21 -0
  206. package/ai/reference/bf_desktop_toast_defaults.md +21 -0
  207. package/ai/reference/bf_dialog_data.md +17 -0
  208. package/ai/reference/bf_labels.md +21 -0
  209. package/ai/reference/bf_overlay_data.md +19 -0
  210. package/ai/reference/bf_overlay_host.md +21 -0
  211. package/ai/reference/bf_sheet_default_snap_points.md +17 -0
  212. package/ai/reference/bf_shell_config.md +21 -0
  213. package/ai/reference/bf_shell_defaults.md +21 -0
  214. package/ai/reference/bf_tab_bar.md +21 -0
  215. package/ai/reference/bf_tab_bar_controller.md +21 -0
  216. package/ai/reference/bf_tab_context.md +21 -0
  217. package/ai/reference/bf_tab_links.md +21 -0
  218. package/ai/reference/bf_theme_storage.md +19 -0
  219. package/ai/reference/create-transition.md +36 -0
  220. package/ai/reference/duration_fast.md +17 -0
  221. package/ai/reference/duration_md.md +17 -0
  222. package/ai/reference/duration_overlay.md +17 -0
  223. package/ai/reference/duration_push.md +17 -0
  224. package/ai/reference/fade-down.md +26 -0
  225. package/ai/reference/fade-in.md +25 -0
  226. package/ai/reference/fade-out.md +25 -0
  227. package/ai/reference/fade-up.md +26 -0
  228. package/ai/reference/inject-shell-mode.md +23 -0
  229. package/ai/reference/install-bf-jsdom-shims.md +31 -0
  230. package/ai/reference/ios_duration.md +17 -0
  231. package/ai/reference/ios_easing.md +17 -0
  232. package/ai/reference/is-text-entry.md +25 -0
  233. package/ai/reference/max_complete_ms.md +17 -0
  234. package/ai/reference/md-transition.md +31 -0
  235. package/ai/reference/md_duration.md +17 -0
  236. package/ai/reference/md_easing.md +17 -0
  237. package/ai/reference/overlay_duration.md +21 -0
  238. package/ai/reference/overlay_easing.md +19 -0
  239. package/ai/reference/overlay_reduced_duration.md +17 -0
  240. package/ai/reference/pop-frames-at.md +25 -0
  241. package/ai/reference/prefers-reduced-motion.md +25 -0
  242. package/ai/reference/provide-bf-defaults.md +33 -0
  243. package/ai/reference/provide-bf-desktop-toast.md +28 -0
  244. package/ai/reference/provide-bf-labels.md +38 -0
  245. package/ai/reference/provide-bf-shell-testing.md +34 -0
  246. package/ai/reference/provide-bf-shell.md +41 -0
  247. package/ai/reference/provide-bf-testing.md +33 -0
  248. package/ai/reference/reduced-transition.md +28 -0
  249. package/ai/reference/reduced_duration.md +17 -0
  250. package/ai/reference/resolve-shell-mode.md +33 -0
  251. package/ai/reference/scale-in.md +25 -0
  252. package/ai/reference/settle-duration.md +25 -0
  253. package/ai/reference/settle-pop.md +33 -0
  254. package/ai/reference/settle_easing.md +17 -0
  255. package/ai/reference/should-complete-pop.md +26 -0
  256. package/ai/reference/slide-down.md +26 -0
  257. package/ai/reference/slide-up.md +26 -0
  258. package/ai/reference/with-bf-shell-routing.md +19 -0
  259. package/bin/bf-ui.js +81 -0
  260. package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
  261. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
  262. package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
  263. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
  264. package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
  265. package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
  266. package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
  267. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
  268. package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
  269. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
  270. package/fesm2022/bitforgehq-ui.mjs +7765 -0
  271. package/fesm2022/bitforgehq-ui.mjs.map +1 -0
  272. package/llms-full.txt +25394 -0
  273. package/llms.txt +329 -0
  274. package/package.json +94 -0
  275. package/theme.css +253 -0
  276. package/types/bitforgehq-ui-icons.d.ts +50 -0
  277. package/types/bitforgehq-ui-mobile.d.ts +2914 -0
  278. package/types/bitforgehq-ui-number-input.d.ts +282 -0
  279. package/types/bitforgehq-ui-overlay.d.ts +1514 -0
  280. package/types/bitforgehq-ui-testing.d.ts +495 -0
  281. package/types/bitforgehq-ui.d.ts +3499 -0
@@ -0,0 +1,522 @@
1
+ # bf-toast
2
+
3
+ Passive status pill with an optional action that never steals focus
4
+
5
+ `BfToastComponent` from `@bitforgehq/ui/mobile` · Overlays · since 0.2.0
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, inject, signal } from '@angular/core';
11
+ import { BfButtonComponent } from '@bitforgehq/ui';
12
+ import {
13
+ BfContentComponent,
14
+ BfHeaderComponent,
15
+ BfPageComponent,
16
+ type BfToastOptions,
17
+ BfToastService,
18
+ } from '@bitforgehq/ui/mobile';
19
+
20
+ /** `bf-toast` showcase: a bottom toast, a top toast and one with an Undo action. */
21
+ @Component({
22
+ selector: 'app-toast-showcase',
23
+ imports: [BfPageComponent, BfHeaderComponent, BfContentComponent, BfButtonComponent],
24
+ template: `
25
+ <bf-page data-showcase="toast">
26
+ <bf-header title="Toast" [largeTitle]="false" />
27
+ <bf-content>
28
+ <div class="flex flex-col gap-4 p-4">
29
+ <p class="text-caption text-text-muted">
30
+ Toasts queue: tap several buttons quickly and each shows once the one before it ends.
31
+ </p>
32
+ <bf-button (clicked)="show({ message: 'Saved' })">Bottom toast</bf-button>
33
+ <bf-button
34
+ variant="tonal"
35
+ (clicked)="show({ message: 'No connection', position: 'top' })"
36
+ >
37
+ Top toast
38
+ </bf-button>
39
+ <bf-button variant="outline" (clicked)="showWithUndo()">Toast with action</bf-button>
40
+ <p data-showcase-result class="text-body">{{ result() }}</p>
41
+ </div>
42
+ </bf-content>
43
+ </bf-page>
44
+ `,
45
+ })
46
+ export class ToastShowcasePage {
47
+ private readonly toasts = inject(BfToastService);
48
+
49
+ protected readonly result = signal('No toast yet.');
50
+
51
+ async show(options: BfToastOptions): Promise<void> {
52
+ const reason = await this.toasts.show(options).afterDismissed;
53
+ this.result.set(`“${options.message}” ended: ${reason}.`);
54
+ }
55
+
56
+ async showWithUndo(): Promise<void> {
57
+ const reason = await this.toasts.show({ message: 'Task deleted', action: { label: 'Undo' } })
58
+ .afterDismissed;
59
+ this.result.set(reason === 'action' ? 'Task restored.' : `“Task deleted” ended: ${reason}.`);
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Overview
65
+
66
+ The status pill that `BfToastService.show()` renders on the overlay core (`passive: true`, no
67
+ backdrop, no focus trap, no inert page and no back handler — it never steals focus): an ink
68
+ `bg-text text-bg rounded-full shadow-floating` pill holding the message (truncated to two lines)
69
+ and an optional action. The pill is not a live region: the service announces each toast once
70
+ through CDK `LiveAnnouncer`. The pointer over it or focus inside it pauses its timer, and
71
+ `Escape` with focus inside dismisses it. `position: 'bottom'` (default) sits 16 px above the
72
+ tab-bar inset (`--bf-tab-bar-inset-global`, published on `<html>` by `bf-tabs`, which leaves
73
+ 8 px above the floating bar, so the toast clears it by 24 px) and the soft keyboard
74
+ (`--bf-toast-keyboard` on the host: `BfKeyboardService.height()` where the keyboard overlays
75
+ the WebView — native iOS — and 0 where the WebView already shrinks); `position: 'top'` sits
76
+ 16 px below the status bar (a top margin of the safe-area inset plus 16 px, outside the pill)
77
+ instead. Not used directly in templates.
78
+
79
+ A status pill. `BfToastService.show(options)` renders `bf-toast` on the overlay core
80
+ (`passive: true`): an ink pill above the floating tab bar and the soft keyboard, with an optional
81
+ action. Only one toast is visible at a time — later calls queue and show once the current one
82
+ ends. You never place `<bf-toast>` in a template yourself.
83
+
84
+ ## When to use
85
+
86
+ Use `BfToastService` in the Capacitor shell for a short status after an action, one at a time above the tab bar: "Saved", "Task deleted. Undo". Use [BfDesktopToastService](https://bitforge-ui.web.app/api/bf-desktop-toast-service) instead in a desktop web app, and [BfAlertDialogService](https://bitforge-ui.web.app/api/bf-alert-dialog-service) when the user must answer.
87
+
88
+ ## Basic usage
89
+
90
+ ```ts
91
+ import { Component, inject, signal } from '@angular/core';
92
+ import { BfButtonComponent } from '@bitforgehq/ui';
93
+ import { BfToastService } from '@bitforgehq/ui/mobile';
94
+
95
+ /**
96
+ * A toast and how it ended.
97
+ *
98
+ * `afterDismissed` resolves with the reason: `timeout`, `action` or `dismiss`.
99
+ *
100
+ * @preview none
101
+ */
102
+ @Component({
103
+ selector: 'app-example-toast-basic',
104
+ imports: [BfButtonComponent],
105
+ template: `
106
+ <div class="flex flex-col items-start gap-3">
107
+ <bf-button (clicked)="open()">Save</bf-button>
108
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
109
+ </div>
110
+ `,
111
+ })
112
+ export class ToastBasicExampleComponent {
113
+ private readonly toasts = inject(BfToastService);
114
+ protected readonly result = signal('');
115
+
116
+ protected async open(): Promise<void> {
117
+ const ref = this.toasts.show({ message: 'Saved' });
118
+ const reason = await ref.afterDismissed; // 'timeout' | 'action' | 'dismiss'
119
+ this.result.set(`The toast ended: ${reason}.`);
120
+ }
121
+ }
122
+ ```
123
+
124
+ `dismiss()` ends the toast right away — closing it if it is the one currently shown, or dropping
125
+ it from the queue before it ever shows.
126
+
127
+ ```ts
128
+ import { Component, inject, signal } from '@angular/core';
129
+ import { BfButtonComponent } from '@bitforgehq/ui';
130
+ import { BfToastService } from '@bitforgehq/ui/mobile';
131
+
132
+ /**
133
+ * A toast that stays until it is dismissed.
134
+ *
135
+ * `duration: 0` keeps "Uploading…" up; `dismiss()` ends it when the upload is done.
136
+ *
137
+ * @preview none
138
+ */
139
+ @Component({
140
+ selector: 'app-example-toast-persistent',
141
+ imports: [BfButtonComponent],
142
+ template: `
143
+ <div class="flex flex-col items-start gap-3">
144
+ <bf-button (clicked)="open()">Upload</bf-button>
145
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
146
+ </div>
147
+ `,
148
+ })
149
+ export class ToastPersistentExampleComponent {
150
+ private readonly toasts = inject(BfToastService);
151
+ protected readonly result = signal('');
152
+ /** Stands in for the real upload. */
153
+ private upload(): Promise<void> {
154
+ return new Promise((resolve) => setTimeout(resolve, 1500));
155
+ }
156
+
157
+ protected async open(): Promise<void> {
158
+ const ref = this.toasts.show({ message: 'Uploading…', duration: 0 });
159
+ await this.upload();
160
+ ref.dismiss();
161
+ this.result.set('Uploaded.');
162
+ }
163
+ }
164
+ ```
165
+
166
+ ## Action
167
+
168
+ `action` renders a real, keyboard-reachable `<button>` inside the pill. Its `handler` runs before
169
+ the toast closes with reason `'action'`. A toast with an action stays until the action or
170
+ `dismiss()` ends it (see Duration), so keyboard and switch users can always reach it.
171
+
172
+ ```ts
173
+ import { Component, inject, signal } from '@angular/core';
174
+ import { BfButtonComponent } from '@bitforgehq/ui';
175
+ import { BfToastService } from '@bitforgehq/ui/mobile';
176
+
177
+ /**
178
+ * A toast with an Undo action.
179
+ *
180
+ * The action's `handler` runs before the toast closes with reason `action`.
181
+ *
182
+ * @preview none
183
+ */
184
+ @Component({
185
+ selector: 'app-example-toast-action',
186
+ imports: [BfButtonComponent],
187
+ template: `
188
+ <div class="flex flex-col items-start gap-3">
189
+ <bf-button variant="danger" (clicked)="open()">Delete task</bf-button>
190
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
191
+ </div>
192
+ `,
193
+ })
194
+ export class ToastActionExampleComponent {
195
+ private readonly toasts = inject(BfToastService);
196
+ protected readonly result = signal('');
197
+
198
+ protected open(): void {
199
+ this.result.set('Task deleted.');
200
+ this.toasts.show({
201
+ message: 'Task deleted',
202
+ action: { label: 'Undo', handler: () => this.result.set('Task restored.') },
203
+ });
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Duration
209
+
210
+ `duration` (ms) is how long the toast stays before it auto-dismisses, resolving `'timeout'`. It
211
+ defaults to `3000` for a toast without an action and to `0` for a toast with one: an action toast
212
+ persists until its action or `dismiss()` ends it, because an "Undo" that vanishes after 3 s is out
213
+ of reach for many keyboard, switch and screen-reader users (WCAG 2.2.1 Timing Adjustable). An
214
+ explicit `duration` always wins. `duration: 0` also suits a toast that tracks an ongoing
215
+ operation (an upload, a sync) rather than a one-off confirmation.
216
+
217
+ ```ts
218
+ import { Component, inject, signal } from '@angular/core';
219
+ import { BfButtonComponent } from '@bitforgehq/ui';
220
+ import { BfToastService } from '@bitforgehq/ui/mobile';
221
+
222
+ /**
223
+ * An action toast with a timeout.
224
+ *
225
+ * An action toast persists until Undo or `dismiss()`; `duration` opts it back into a timeout.
226
+ *
227
+ * @preview none
228
+ */
229
+ @Component({
230
+ selector: 'app-example-toast-duration',
231
+ imports: [BfButtonComponent],
232
+ template: `
233
+ <div class="flex flex-col items-start gap-3">
234
+ <bf-button (clicked)="open()">Delete task</bf-button>
235
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
236
+ </div>
237
+ `,
238
+ })
239
+ export class ToastDurationExampleComponent {
240
+ private readonly toasts = inject(BfToastService);
241
+ protected readonly result = signal('');
242
+
243
+ protected open(): void {
244
+ const undo = (): void => this.result.set('Task restored.');
245
+ this.result.set('Task deleted.');
246
+ this.toasts.show({
247
+ message: 'Task deleted',
248
+ action: { label: 'Undo', handler: undo },
249
+ duration: 8000,
250
+ });
251
+ }
252
+ }
253
+ ```
254
+
255
+ The timer pauses while the pointer is over the toast or focus is inside it, and resumes with the
256
+ time that was left once both have left — a toast never times out under the reader's pointer or
257
+ while its action is focused.
258
+
259
+ ## Position
260
+
261
+ `position` is `'bottom'` (default) or `'top'`. `'bottom'` sits 16 px above the tab-bar inset
262
+ (`--bf-tab-bar-inset-global`, published on `<html>` by `bf-tabs`; it already leaves 8 px above the
263
+ floating bar, so the toast clears the bar by 24 px) and the soft keyboard,
264
+ recomputed live as either changes — `BfKeyboardService.height()` where the keyboard overlays the
265
+ WebView (native iOS), nothing where the WebView already shrinks for it (Android, the web); `'top'` sits 16 px below the status bar (a top margin of the safe-area inset plus 16 px), for a toast that would
266
+ otherwise sit under a keyboard-focused control.
267
+
268
+ ```ts
269
+ import { Component, inject } from '@angular/core';
270
+ import { BfButtonComponent } from '@bitforgehq/ui';
271
+ import { BfToastService } from '@bitforgehq/ui/mobile';
272
+
273
+ /**
274
+ * A toast at the top of the screen.
275
+ *
276
+ * `position: 'top'` shows it under the status bar instead of above the tab bar.
277
+ *
278
+ * @preview none
279
+ */
280
+ @Component({
281
+ selector: 'app-example-toast-position',
282
+ imports: [BfButtonComponent],
283
+ template: `<bf-button (clicked)="open()">Go offline</bf-button>`,
284
+ })
285
+ export class ToastPositionExampleComponent {
286
+ private readonly toasts = inject(BfToastService);
287
+
288
+ protected open(): void {
289
+ this.toasts.show({ message: 'No connection', position: 'top' });
290
+ }
291
+ }
292
+ ```
293
+
294
+ ## Queueing
295
+
296
+ `show()` never interrupts a visible timed toast: a second call while one is up queues in order and
297
+ shows once the first ends (however it ends — timeout, action or `dismiss()`). Each call still
298
+ returns its own `BfToastRef` immediately, so callers can `dismiss()` or await `afterDismissed` on a
299
+ toast that hasn't shown yet.
300
+
301
+ Persistent toasts (`duration: 0`, which is the default for a toast with an action) never block the
302
+ queue: the next `show()` replaces one, visible or still waiting, and it resolves `'dismiss'` — a
303
+ superseded "Undo" is stale anyway (the Material snackbar model). A navigation (a push, a pop, a
304
+ tab switch) never closes a toast, so a "Saved" survives the navigation that follows the save. Keep
305
+ the ref of an action toast and `dismiss()` it yourself as soon as it goes stale, for example when the user navigates away or
306
+ takes the next action.
307
+
308
+ ```ts
309
+ import { Component, inject, signal } from '@angular/core';
310
+ import { BfButtonComponent } from '@bitforgehq/ui';
311
+ import { type BfToastRef, BfToastService } from '@bitforgehq/ui/mobile';
312
+
313
+ /**
314
+ * One Undo toast at a time.
315
+ *
316
+ * Each archive dismisses the previous Undo toast before it shows its own.
317
+ *
318
+ * @preview none
319
+ */
320
+ @Component({
321
+ selector: 'app-example-toast-queueing',
322
+ imports: [BfButtonComponent],
323
+ template: `
324
+ <div class="flex flex-col items-start gap-3">
325
+ <bf-button (clicked)="open()">Archive</bf-button>
326
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
327
+ </div>
328
+ `,
329
+ })
330
+ export class ToastQueueingExampleComponent {
331
+ private readonly toasts = inject(BfToastService);
332
+ protected readonly result = signal('');
333
+ private undo: BfToastRef | null = null;
334
+
335
+ protected open(): void {
336
+ this.undo?.dismiss();
337
+ this.undo = this.toasts.show({
338
+ message: 'Archived',
339
+ action: { label: 'Undo', handler: () => this.result.set('Restored.') },
340
+ });
341
+ this.result.set('Archived.');
342
+ }
343
+ }
344
+ ```
345
+
346
+ ## Desktop
347
+
348
+ `BfToastService` shows one phone-width toast at a time and is meant for the Capacitor shell
349
+ (`provideBfShell()`). On a desktop web app, use `BfDesktopToastService` from
350
+ `@bitforgehq/ui/overlay` instead: it needs only `@angular/cdk`, no Capacitor and no router, stacks
351
+ up to three cards in the bottom-right corner and follows the same rules (timing, actions, replacing,
352
+ one announcement per toast). A toast never locks the page's scroll or makes it `inert`.
353
+
354
+ ```ts
355
+ import { Component, inject, signal } from '@angular/core';
356
+ import { BfButtonComponent } from '@bitforgehq/ui';
357
+ import { BfDesktopToastService } from '@bitforgehq/ui/overlay';
358
+
359
+ /**
360
+ * A toast and how it ended.
361
+ *
362
+ * `afterDismissed` resolves with the reason: `timeout`, `action` or `dismiss`.
363
+ */
364
+ @Component({
365
+ selector: 'app-example-desktop-toast-basic',
366
+ imports: [BfButtonComponent],
367
+ template: `
368
+ <div class="flex flex-col items-start gap-3">
369
+ <bf-button (clicked)="save()">Save</bf-button>
370
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
371
+ </div>
372
+ `,
373
+ })
374
+ export class DesktopToastBasicExampleComponent {
375
+ private readonly toasts = inject(BfDesktopToastService);
376
+ protected readonly result = signal('');
377
+
378
+ protected async save(): Promise<void> {
379
+ const ref = this.toasts.show({ message: 'Changes saved' });
380
+ const reason = await ref.afterDismissed; // 'timeout' | 'action' | 'dismiss'
381
+ this.result.set(`The toast ended: ${reason}.`);
382
+ }
383
+ }
384
+ ```
385
+
386
+ ## Theming
387
+
388
+ - Pill: `bg-text text-bg` (the ink surface, inverted from the rest of the UI), `rounded-full`,
389
+ `shadow-floating`.
390
+ - Action label: `text-bg` with an underline — `bf-button`'s `text` variant uses `text-primary`,
391
+ which has no contrast against the ink pill, so the action is a plain styled `<button>` instead.
392
+ Its hit area is 44 px tall (`min-h-hit`) and reaches into the pill's padding with negative margins,
393
+ so the pill keeps its 48 px height and its width. It shows a pointer cursor and fades to 80 %
394
+ opacity on hover.
395
+ - Width: at most the viewport minus the 16 px gutter on each side.
396
+
397
+ ## Accessibility
398
+
399
+ - Announcement: `BfToastService` announces each toast exactly once, politely, through the CDK
400
+ `LiveAnnouncer` (a persistent live region that exists before the toast does) as the toast
401
+ shows: the message, followed by the action label when there is one ("Task deleted. Undo"). A
402
+ queued toast is announced when it shows, never while it waits. The pill itself is not a live
403
+ region (no `role`, no `aria-live`), and the overlay pane around it has no role either, so
404
+ nothing is read twice.
405
+ - Focus: the toast is opened `passive` on the overlay core: no backdrop, no focus trap, the page
406
+ behind it is never made `inert`, and it never moves focus — the action button is reachable by
407
+ Tab like any other control on the page.
408
+ - Keyboard: `Escape` while focus is inside the toast dismisses it (resolving `'dismiss'`); the key
409
+ is claimed there, so it never also reaches a dialog. `Escape` elsewhere on the page is not the
410
+ toast's. There is no close button.
411
+ - Focus return: when a toast that still holds focus closes for any reason (its action, `Escape`,
412
+ `dismiss()`, a replacing `show()`), focus returns, with `preventScroll`, to the element focused
413
+ last outside the toast — the one focused when it opened, or one focused since — if that element
414
+ is still in the page. Once focus has fallen to the page body (a click on empty space) there is no
415
+ such element and focus is not moved. A toast that no longer holds focus leaves it alone, and an
416
+ action `handler` that moves focus elsewhere keeps that. A mouse click on the action focuses it
417
+ in most browsers, so it returns focus the same way.
418
+ - Timing: action toasts persist by default, and every toast pauses while hovered or focused (see
419
+ Duration).
420
+ - Only one toast is ever visible, so announcements don't overlap. Under `prefers-reduced-motion` it
421
+ fades in and out without the 12 px slide.
422
+
423
+ ### Keyboard
424
+
425
+ | Key | Action |
426
+ | ----------------------------------- | --------------------------------------------------------------------------------------- |
427
+ | <kbd>Tab</kbd> | Reaches the action button in page order, like any control; the toast never takes focus |
428
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | Runs the focused action; the toast closes with `'action'` |
429
+ | <kbd>Escape</kbd> | With focus inside the toast: dismisses it (`'dismiss'`); a dialog underneath stays open |
430
+
431
+ ## Notes
432
+
433
+ ### Do
434
+
435
+ - Keep the message short — it truncates to two lines.
436
+ - Keep the ref of a persistent toast and `dismiss()` it once it is stale (navigation, the next
437
+ action); the next `show()` replaces it anyway, but until then it stays up.
438
+ - Give `action.handler` a fast, synchronous-feeling effect (undo, retry) — the toast closes right
439
+ after it runs.
440
+ - Leave action toasts on their default (no timeout); if you set a `duration` on one, make it
441
+ generous and offer the same action somewhere persistent too.
442
+
443
+ ### Don't
444
+
445
+ - Don't rely on the toast for anything the user must see — it can be missed, it waits in the queue
446
+ behind the one already up, and a persistent toast is replaced by the next `show()`.
447
+ - Don't put more than one action in the pill; it holds a single action button.
448
+ - Don't use a toast for anything the user must actively respond to before continuing — it never
449
+ traps focus or blocks the page; reach for a dialog or sheet instead.
450
+
451
+ ## Migration notes
452
+
453
+ ### 0.3.0
454
+
455
+ The toast engine (timing, hold, replace, focus return, announcements, teardown) moved into
456
+ `@bitforgehq/ui/overlay`, shared with the new desktop toast; `BfToastService` renders one toast at
457
+ a time on it exactly as before. `BfToastRef`, `BfToastDismissReason` and `BfToastAction` are now
458
+ also exported from `@bitforgehq/ui/overlay`; the `/mobile` exports stay (not deprecated) and are
459
+ the same types and class. No behaviour change: a navigation never closes a toast (it opts out of
460
+ the core's new `closeOnNavigation`).
461
+
462
+ JSDoc of `toast.component.ts` for the API reference: `@summary`/`@since` on the exported symbols. No
463
+ behaviour change.
464
+
465
+ JSDoc of `toast.service.ts` for the API reference: `@summary`/`@since` on the exported symbols;
466
+ `@default` on the option properties the code applies; `@param` docs. No behaviour change.
467
+
468
+ **Behaviour:** `--bf-toast-keyboard` is written in the render phase (`afterRenderEffect`) instead
469
+ of an `effect`; internal timing only, no API change.
470
+
471
+ ### 0.2.0
472
+
473
+ New `BfToastService.show({ message, action?, duration, position })` in `@bitforgehq/ui/mobile`, a
474
+ queued status pill on the overlay core opened `passive: true` (no backdrop, no focus trap, the page
475
+ stays interactive and it never counts towards `BfOverlayStack.count()`). Only one toast shows at a
476
+ time; later `show()` calls queue in order behind a timed toast and appear once it ends (a persistent
477
+ one gives way, see the behaviour changes below). It returns a `BfToastRef` (`dismiss()`,
478
+ `afterDismissed: Promise<'timeout' | 'action' | 'dismiss'>`) — the toast resolves `'timeout'` after
479
+ `duration` ms (`0` keeps it up until `dismiss()` or its action; the default is 3000 without an
480
+ action and 0 with one, see below), `'action'` when the optional action button is clicked (its
481
+ `handler` runs first), and `'dismiss'` from `dismiss()`, whether the toast is showing or still
482
+ queued. `bf-toast` is an ink `bg-text text-bg rounded-full shadow-floating` pill (message
483
+ `line-clamp-2`, announced through `LiveAnnouncer`, see the "Changed" note below); the action renders
484
+ as a plain underlined `text-bg` button rather than `bf-button`'s `text` variant, which has no
485
+ contrast on the ink surface. `position: 'bottom'` (default) keeps 16 px clear of the floating tab
486
+ bar (`--bf-tab-bar-inset-global` on `<html>`, see `### tabs`) and the soft keyboard, written live as
487
+ `--bf-toast-keyboard`: `BfKeyboardService.height()` where the keyboard overlays the WebView (native
488
+ iOS), `0` where the WebView already resizes (Android, the web); `position: 'top'` sits 16 px below
489
+ the status bar instead, as a top margin (`mt-[calc(var(--spacing-safe-top)+var(--spacing)*4)]`)
490
+ outside the pill: an earlier `pt-safe-top` on the host overrode the pill's own `py-3` (0 top padding
491
+ on the web, about 50 px on a notched iPhone). Enter fades in with a 12 px slide from its anchored
492
+ edge, exit is a plain fade; both collapse to a fade-only under `prefers-reduced-motion`.
493
+ `provideBfShell()` now also provides `BfToastService`. The action button has a 44 px hit area
494
+ (`min-h-hit px-3`) that reaches into the pill's padding (`-my-2.5 -mx-3`), so the pill keeps its 48
495
+ px height and width.
496
+
497
+ Fixed: the action button shows a pointer cursor and fades to 80 % opacity on hover.
498
+
499
+ Changed: with the new tab-bar inset (see `### tabs`, which now includes 8 px above the bar) a
500
+ bottom toast sits 16 px above the inset and so clears the floating tab bar by 24 px (was 16 px).
501
+
502
+ Behaviour change: a toast with an `action` no longer auto-dismisses by default (`duration`
503
+ defaults to `0` with an action, still `3000` without one; an explicit `duration` wins), so an
504
+ "Undo" stays reachable for keyboard, switch and screen-reader users (WCAG 2.2.1). Every toast's
505
+ timer pauses while the pointer is over it or focus is inside it and resumes with the remaining
506
+ time. When a toast that holds focus closes (action or `dismiss()`), focus returns with
507
+ `preventScroll` to the element it came from (the first `focusin`'s `relatedTarget`, else the
508
+ element focused when it opened), if still in the page; before, it fell to `<body>`.
509
+
510
+ Changed: toasts are announced through CDK `LiveAnnouncer` (`announce(text, 'polite')`, once per
511
+ toast as it shows; the text is the message plus the action label). The `bf-toast` host no longer
512
+ carries `role="status"`/`aria-live="polite"`: a live region inserted together with its text was
513
+ not reliably announced. Tests locating the pill by `role="status"` should use `bf-toast`.
514
+
515
+ Behaviour change: persistent toasts (`duration` 0, the default with an action) never block the
516
+ queue — the next `show()` dismisses one, visible or still queued, resolving `'dismiss'` (the
517
+ Material snackbar model; a timed toast still queues the next one). `Escape` while focus is inside a
518
+ toast dismisses it (`'dismiss'`) and is claimed there. Focus returns only while the toast still
519
+ holds focus at close time, to the element focused last outside it; once focus has fallen to the
520
+ page body it is not moved to the element that was focused when the toast opened. The service ends
521
+ every toast (timers and document listeners included) when its injector is destroyed, and the
522
+ announcement joins a message ending in `:`, `;` or `,` to the action label with a space.