@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,509 @@
1
+ # bf-alert-dialog
2
+
3
+ Centred confirmation dialog with a title, message and a row of role-styled buttons
4
+
5
+ `BfAlertDialogComponent` 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
+ BfAlertDialogService,
14
+ BfContentComponent,
15
+ BfHeaderComponent,
16
+ BfPageComponent,
17
+ } from '@bitforgehq/ui/mobile';
18
+
19
+ /**
20
+ * `bf-alert-dialog` showcase: a confirmation with a cancel and a destructive button, and an
21
+ * informational alert with a single OK.
22
+ */
23
+ @Component({
24
+ selector: 'app-alert-dialog-showcase',
25
+ imports: [BfPageComponent, BfHeaderComponent, BfContentComponent, BfButtonComponent],
26
+ template: `
27
+ <bf-page data-showcase="alert-dialog">
28
+ <bf-header title="Alert dialog" [largeTitle]="false" />
29
+ <bf-content>
30
+ <div class="flex flex-col gap-4 p-4">
31
+ <p class="text-caption text-text-muted">
32
+ An alert must be answered: there is no backdrop dismiss, and Escape answers with the
33
+ cancel button when there is one.
34
+ </p>
35
+ <bf-button variant="danger" (clicked)="confirmDelete()">Delete draft</bf-button>
36
+ <bf-button variant="tonal" (clicked)="inform()">Show sync status</bf-button>
37
+ <p data-showcase-result class="text-body">{{ result() }}</p>
38
+ </div>
39
+ </bf-content>
40
+ </bf-page>
41
+ `,
42
+ })
43
+ export class AlertDialogShowcasePage {
44
+ private readonly alerts = inject(BfAlertDialogService);
45
+
46
+ protected readonly result = signal('No answer yet.');
47
+
48
+ async confirmDelete(): Promise<void> {
49
+ const answer = await this.alerts.open({
50
+ title: 'Delete draft?',
51
+ message: 'This cannot be undone.',
52
+ buttons: [
53
+ { label: 'Cancel', role: 'cancel' },
54
+ { label: 'Delete', role: 'destructive' },
55
+ ],
56
+ });
57
+ this.report(answer);
58
+ }
59
+
60
+ async inform(): Promise<void> {
61
+ const answer = await this.alerts.open({
62
+ title: 'All synced',
63
+ message: 'Your notes are up to date on every device.',
64
+ });
65
+ this.report(answer);
66
+ }
67
+
68
+ /** A navigation closes an alert without a cancel button with no answer (`undefined`). */
69
+ private report(answer: string | undefined): void {
70
+ this.result.set(answer === undefined ? 'Closed without an answer.' : `Answered “${answer}”.`);
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Overview
76
+
77
+ The alert dialog card that `BfAlertDialogService.open()` renders on the overlay core
78
+ (`position: 'center'`): an `h2.text-heading` title, an optional `text-body text-text-secondary`
79
+ message, and a right-aligned row of `bf-button`s (`cancel`-role `text`, `destructive`-role
80
+ `danger`, the first plain button `primary`, further plain ones `tonal`). Focus starts on the
81
+ `cancel`-role button, else the first one. Tapping a button resolves the overlay with its
82
+ `id ?? label`. There is no backdrop dismiss; `Escape` and the hardware
83
+ back button are handled by `BfAlertDialogService` (the cancel button's answer, or claimed and
84
+ ignored when there is none). Not used directly in templates.
85
+
86
+ A centred confirmation dialog. `BfAlertDialogService.open({ title, message?, buttons? })` renders
87
+ `bf-alert-dialog` on the overlay core: a title, an optional message and a row of buttons. It
88
+ resolves a `Promise<string>` with the tapped button's `id`, or its label when it has none — always
89
+ a string, never `undefined`, because the dialog cannot be dismissed without answering it. You never place `<bf-alert-dialog>`
90
+ in a template yourself.
91
+
92
+ ## When to use
93
+
94
+ Use `BfAlertDialogService` in the Capacitor shell for a short question the user must answer before going on: discard a draft, delete an item. Use [BfActionSheetService](https://bitforge-ui.web.app/api/bf-action-sheet-service) instead for a choice between several actions, and [BfDialogService](https://bitforge-ui.web.app/api/bf-dialog-service) `confirm()` on desktop.
95
+
96
+ ## Basic usage
97
+
98
+ Without `buttons`, the dialog shows a single button labelled with `provideBfLabels()`'s `ok` ("OK").
99
+
100
+ ```ts
101
+ import { Component, inject, signal } from '@angular/core';
102
+ import { BfButtonComponent } from '@bitforgehq/ui';
103
+ import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
104
+
105
+ /**
106
+ * An alert with a message and the default OK button.
107
+ *
108
+ * `open()` resolves once the alert is answered.
109
+ *
110
+ * @preview none
111
+ */
112
+ @Component({
113
+ selector: 'app-example-alert-dialog-basic',
114
+ imports: [BfButtonComponent],
115
+ template: `
116
+ <div class="flex flex-col items-start gap-3">
117
+ <bf-button (clicked)="open()">Sync</bf-button>
118
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
119
+ </div>
120
+ `,
121
+ })
122
+ export class AlertDialogBasicExampleComponent {
123
+ private readonly alerts = inject(BfAlertDialogService);
124
+ protected readonly result = signal('');
125
+
126
+ protected async open(): Promise<void> {
127
+ await this.alerts.open({
128
+ title: 'Sync failed',
129
+ message: 'Check your connection and try again.',
130
+ });
131
+ this.result.set('Alert closed.');
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Confirmation with a cancel button
137
+
138
+ Give `buttons` a `cancel`-role entry to let the dialog be dismissed by `Escape` or the hardware
139
+ back button too — both resolve with that button's answer. The cancel button also takes the
140
+ initial focus, wherever it sits in the row.
141
+
142
+ ```ts
143
+ import { Component, inject, signal } from '@angular/core';
144
+ import { BfButtonComponent } from '@bitforgehq/ui';
145
+ import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
146
+
147
+ /**
148
+ * A confirmation with a cancel button.
149
+ *
150
+ * The `cancel`-role button answers a dismissal; the destructive one renders in the danger colour.
151
+ *
152
+ * @preview none
153
+ */
154
+ @Component({
155
+ selector: 'app-example-alert-dialog-confirm',
156
+ imports: [BfButtonComponent],
157
+ template: `
158
+ <div class="flex flex-col items-start gap-3">
159
+ <bf-button variant="danger" (clicked)="open()">Delete list…</bf-button>
160
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
161
+ </div>
162
+ `,
163
+ })
164
+ export class AlertDialogConfirmExampleComponent {
165
+ private readonly alerts = inject(BfAlertDialogService);
166
+ protected readonly result = signal('');
167
+
168
+ protected async open(): Promise<void> {
169
+ const answer = await this.alerts.open({
170
+ title: 'Delete list?',
171
+ message: 'This cannot be undone.',
172
+ buttons: [
173
+ { id: 'cancel', label: 'Cancel', role: 'cancel' },
174
+ { id: 'delete', label: 'Delete', role: 'destructive' },
175
+ ],
176
+ });
177
+ this.result.set(answer === 'delete' ? 'Deleted.' : 'Kept.');
178
+ }
179
+ }
180
+ ```
181
+
182
+ ## Answers with ids
183
+
184
+ Give each button an `id` and compare against it: `open()` resolves `id ?? label`, so the check
185
+ keeps working when the labels are translated, and two buttons that end up with the same label are
186
+ still told apart. Without an `id` the label is the answer, as before.
187
+
188
+ ```ts
189
+ import { Component, inject, signal } from '@angular/core';
190
+ import { BfButtonComponent } from '@bitforgehq/ui';
191
+ import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
192
+
193
+ /** The app's translated labels (German here): the ids, not these, carry the answer. */
194
+ const LABELS = { cancel: 'Abbrechen', device: 'Auf diesem Gerät', cloud: 'In der Cloud' };
195
+
196
+ /**
197
+ * Buttons answered by id.
198
+ *
199
+ * The ids stay the same whatever the translated labels say.
200
+ *
201
+ * @preview none
202
+ */
203
+ @Component({
204
+ selector: 'app-example-alert-dialog-ids',
205
+ imports: [BfButtonComponent],
206
+ template: `
207
+ <div class="flex flex-col items-start gap-3">
208
+ <bf-button (clicked)="open()">Save a copy</bf-button>
209
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
210
+ </div>
211
+ `,
212
+ })
213
+ export class AlertDialogIdsExampleComponent {
214
+ private readonly alerts = inject(BfAlertDialogService);
215
+ protected readonly result = signal('');
216
+
217
+ protected async open(): Promise<void> {
218
+ const where = await this.alerts.open({
219
+ title: 'Save a copy?',
220
+ buttons: [
221
+ { id: 'cancel', label: LABELS.cancel, role: 'cancel' },
222
+ { id: 'device', label: LABELS.device },
223
+ { id: 'cloud', label: LABELS.cloud },
224
+ ],
225
+ });
226
+ this.result.set(where ?? 'Dismissed.');
227
+ }
228
+ }
229
+ ```
230
+
231
+ ## Button variants
232
+
233
+ Buttons render left to right in the order given: a `cancel`-role button is `text`, a
234
+ `destructive`-role button is `danger`, the first plain button is `primary`, and any further plain
235
+ button is `tonal`.
236
+
237
+ ```ts
238
+ import { Component, inject, signal } from '@angular/core';
239
+ import { BfButtonComponent } from '@bitforgehq/ui';
240
+ import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
241
+
242
+ /**
243
+ * The button variants of an alert.
244
+ *
245
+ * The first plain button is primary, further plain ones tonal, and a destructive one danger.
246
+ *
247
+ * @preview none
248
+ */
249
+ @Component({
250
+ selector: 'app-example-alert-dialog-variants',
251
+ imports: [BfButtonComponent],
252
+ template: `
253
+ <div class="flex flex-col items-start gap-3">
254
+ <bf-button (clicked)="open()">Close the draft</bf-button>
255
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
256
+ </div>
257
+ `,
258
+ })
259
+ export class AlertDialogVariantsExampleComponent {
260
+ private readonly alerts = inject(BfAlertDialogService);
261
+ protected readonly result = signal('');
262
+
263
+ protected async open(): Promise<void> {
264
+ const answer = await this.alerts.open({
265
+ title: 'Keep editing?',
266
+ buttons: [
267
+ { label: 'Discard', role: 'destructive' },
268
+ { label: 'Save as draft' }, // tonal (a plain button after the primary one)
269
+ { label: 'Save' }, // primary (the first plain button)
270
+ ],
271
+ });
272
+ this.result.set(answer ?? 'Dismissed.');
273
+ }
274
+ }
275
+ ```
276
+
277
+ ## Navigation
278
+
279
+ A navigation to another URL path closes the alert: `open()` resolves the cancel button's answer
280
+ when it has one, else `undefined` (so `open()` returns `Promise<string | undefined>`; treat
281
+ `undefined` like cancel). A query- or fragment-only change leaves it open. In the `/mobile` shell
282
+ every push, every pop (the header back button, swipe-back, `BfNavController.back()`) and every tab
283
+ switch changes the path, so an alert opened on a page closes when that page is left. The Android
284
+ hardware back button with an alert open never pops the page (its handler runs at priority 100,
285
+ above the page pop): it answers with the cancel button, or is ignored without one. Android back
286
+ in a browser without Capacitor (Chrome, a PWA) does the same where `CloseWatcher` exists, and
287
+ the URL stays: an alert without a cancel button swallows it and stays open. An alert
288
+ opened before the first navigation ends closes when the start URL redirects. Pass
289
+ `closeOnNavigation: false` to keep it open until a button is tapped.
290
+
291
+ ```ts
292
+ import { Component, inject, signal } from '@angular/core';
293
+ import { BfButtonComponent } from '@bitforgehq/ui';
294
+ import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
295
+
296
+ /**
297
+ * An alert that stays open across navigations.
298
+ *
299
+ * `closeOnNavigation: false` keeps it open when the route changes underneath.
300
+ *
301
+ * @preview none
302
+ */
303
+ @Component({
304
+ selector: 'app-example-alert-dialog-navigation',
305
+ imports: [BfButtonComponent],
306
+ template: `
307
+ <div class="flex flex-col items-start gap-3">
308
+ <bf-button (clicked)="open()">Sync</bf-button>
309
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
310
+ </div>
311
+ `,
312
+ })
313
+ export class AlertDialogNavigationExampleComponent {
314
+ private readonly alerts = inject(BfAlertDialogService);
315
+ protected readonly result = signal('');
316
+
317
+ protected async open(): Promise<void> {
318
+ const answer = await this.alerts.open({ title: 'Sync failed', closeOnNavigation: false });
319
+ this.result.set(answer ?? 'Dismissed.');
320
+ }
321
+ }
322
+ ```
323
+
324
+ ## Labels and the injector
325
+
326
+ Default strings come from `provideBfLabels()` (`ok`, "OK"). The service is a root singleton, so it reads
327
+ the app-level labels; to use a feature's own `provideBfLabels()` (in a component's `providers`),
328
+ pass that component's injector. It then also becomes the parent of the dialog's content injector,
329
+ so the content resolves the feature's providers.
330
+
331
+ ```ts
332
+ import { Component, Injector, inject } from '@angular/core';
333
+ import { BfButtonComponent, provideBfLabels } from '@bitforgehq/ui';
334
+ import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
335
+
336
+ /**
337
+ * An alert that reads a feature's labels.
338
+ *
339
+ * This feature provides German labels; passing the opener's injector is what makes the alert's
340
+ * default button read "Verstanden" instead of the app's "OK".
341
+ *
342
+ * @preview none
343
+ */
344
+ @Component({
345
+ selector: 'app-example-alert-dialog-injector',
346
+ imports: [BfButtonComponent],
347
+ providers: [provideBfLabels({ ok: 'Verstanden' })],
348
+ template: `<bf-button (clicked)="open()">Speichern</bf-button>`,
349
+ })
350
+ export class AlertDialogInjectorExampleComponent {
351
+ private readonly alerts = inject(BfAlertDialogService);
352
+ private readonly injector = inject(Injector);
353
+
354
+ protected open(): void {
355
+ void this.alerts.open({ title: 'Gespeichert', injector: this.injector });
356
+ }
357
+ }
358
+ ```
359
+
360
+ ## Desktop
361
+
362
+ `bf-alert-dialog` is phone-shaped and meant for the Capacitor shell (`provideBfShell()`). On a
363
+ desktop web app, use the desktop overlays from `@bitforgehq/ui/overlay` instead: they need only
364
+ `@angular/cdk`, no Capacitor and no router. A desktop dialog locks the page's scroll, makes the page
365
+ behind it `inert`, and traps and restores focus.
366
+
367
+ `BfDialogService.confirm()` is the desktop alert dialog: the same `buttons` (ids, `cancel` and
368
+ `destructive` roles), focus on the cancel button, and an answer of `id ?? label`.
369
+
370
+ ```ts
371
+ import { Component, inject, signal } from '@angular/core';
372
+ import { BfButtonComponent } from '@bitforgehq/ui';
373
+ import { BfDialogService } from '@bitforgehq/ui/overlay';
374
+
375
+ /**
376
+ * A destructive confirmation.
377
+ *
378
+ * `confirm()` resolves with the pressed button's id: `delete`, `cancel`, or the cancel answer on
379
+ * `Escape`.
380
+ */
381
+ @Component({
382
+ selector: 'app-example-dialog-confirm',
383
+ imports: [BfButtonComponent],
384
+ template: `
385
+ <div class="flex flex-col items-start gap-3">
386
+ <bf-button variant="danger" (clicked)="remove()">Delete project…</bf-button>
387
+ <p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
388
+ </div>
389
+ `,
390
+ })
391
+ export class DialogConfirmExampleComponent {
392
+ private readonly dialogs = inject(BfDialogService);
393
+ protected readonly result = signal('');
394
+
395
+ protected async remove(): Promise<void> {
396
+ const answer = await this.dialogs.confirm({
397
+ title: 'Delete project?',
398
+ message: 'Its tasks and files are deleted too. This cannot be undone.',
399
+ buttons: [
400
+ { id: 'cancel', label: 'Cancel', role: 'cancel' },
401
+ { id: 'delete', label: 'Delete', role: 'destructive' },
402
+ ],
403
+ });
404
+ this.result.set(answer === 'delete' ? 'Deleted (not really).' : 'Kept.');
405
+ }
406
+ }
407
+ ```
408
+
409
+ ## Theming
410
+
411
+ - Card: `bg-surface`, `rounded-card` (`--radius-card`), `shadow-floating`, fixed `w-75` (300 px)
412
+ (`max-w-[calc(100vw-2*var(--spacing-gutter))]` on narrow viewports).
413
+ - Title: `text-heading`; message: `text-body text-text-secondary`.
414
+ - Buttons: `bf-button` (`text`, `danger`, `primary`, `tonal` — see Button variants).
415
+ - Backdrop: `bf-overlay-backdrop` from the overlay core (a black scrim: 40 % in light mode, 60 % in dark), rendered but never
416
+ dismissing.
417
+
418
+ ## Accessibility
419
+
420
+ The overlay panel is a `role="alertdialog"` with `aria-modal="true"`, `aria-labelledby`
421
+ pointing at the title and, when there is a `message`, `aria-describedby` pointing at it; the page stack behind it is `inert`. Focus lands on the `cancel`-role
422
+ button, else on the first button (the title and message hold no focusable content), so Enter on
423
+ open never confirms a destructive action ([APG alert dialog](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/):
424
+ focus the least destructive choice). Tab is trapped inside, and focus returns to the
425
+ element that opened the dialog on close. There is no backdrop dismiss. `Escape` and the hardware
426
+ back button resolve with the cancel button's `id ?? label` when `buttons` has one; with none, both are
427
+ claimed and otherwise ignored, so the dialog stays open — **an alert must have a cancel button to
428
+ be dismissible** any other way than tapping a button. Either way the key stops at the alert: an
429
+ alert opened over a sheet never lets `Escape` close the sheet underneath. Under `prefers-reduced-motion` the card
430
+ cross-fades in and out instead of scaling.
431
+
432
+ ### Keyboard
433
+
434
+ | Key | Action |
435
+ | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
436
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between the buttons, wrapping inside the dialog; focus starts on the `cancel`-role button, else the first |
437
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | Activates the focused button |
438
+ | <kbd>Escape</kbd> | Answers with the `cancel`-role button; without one, it is claimed and ignored |
439
+
440
+ ## Notes
441
+
442
+ ### Do
443
+
444
+ - Give every dismissible confirmation a `cancel`-role button, so `Escape` and the hardware back
445
+ button work the way people expect.
446
+ - Keep `message` short and put the consequence in it ("This cannot be undone") rather than in the
447
+ title.
448
+ - Use `role: 'destructive'` on the button that causes data loss, and word it as the action
449
+ ("Delete", not "Yes").
450
+
451
+ ### Don't
452
+
453
+ - Don't open an alert with no cancel button unless every button is a safe, equally valid answer
454
+ (an informational dialog with just "OK") — otherwise give it a cancel button.
455
+ - Don't stack more than two or three buttons; use an action sheet (`BfActionSheetService`) for a
456
+ longer list of choices.
457
+ - Don't rely on a backdrop tap to close the dialog — there is none, by design, so the user must
458
+ make a choice.
459
+
460
+ ## Migration notes
461
+
462
+ ### 0.3.0
463
+
464
+ New exported `BfAlertDialogButtonRole` (`'cancel' | 'destructive'`), the type of
465
+ `BfAlertDialogButton.role` (was an inline union with the same values).
466
+
467
+ **Type:** `open()` returns `Promise<string | undefined>` (was `Promise<string>`): a navigation to
468
+ another URL path closes the alert and resolves the cancel button's answer, else `undefined`. New
469
+ `closeOnNavigation` option (default true). Migration: handle `undefined` like cancel (see
470
+ Migration 18).
471
+
472
+ **Behaviour:** the default button (no `buttons`) is labelled with `provideBfLabels()`'s `ok`
473
+ ("OK").
474
+
475
+ New `injector` option: labels resolve from it per call (default the root injector) and it becomes
476
+ the parent of the content's injector, so a feature-level `provideBfLabels()` and the feature's
477
+ other providers reach the dialog.
478
+
479
+ `BfAlertDialogButton` gains `id?: string`; `open()` resolves the tapped button's `id ?? label`,
480
+ and `Escape` and the hardware back button the cancel button's. Buttons render with
481
+ `track $index`, so two buttons with the same label both render.
482
+
483
+ **Behaviour:** focus starts on the `cancel`-role button, else the first button (APG alert dialog),
484
+ even when a destructive button comes first (see Migration 17).
485
+
486
+ JSDoc of `alert-dialog.service.ts` for the API reference: `@summary`/`@since` on the exported
487
+ symbols; `@default` on the option properties the code applies; `@param` docs. No behaviour change.
488
+
489
+ The docs say that Android back in a browser with `CloseWatcher` answers with the cancel button,
490
+ and that an alert without one swallows it; see `### overlay core` and Migration 35.
491
+
492
+ ### 0.2.0
493
+
494
+ New `BfAlertDialogService.open({ title, message?, buttons? })` in `@bitforgehq/ui/mobile`,
495
+ resolving `Promise<string>` with the tapped button's label. Renders `bf-alert-dialog` on the
496
+ overlay core (`position: 'center'`, back priority 100, not counted as a modal, no backdrop
497
+ dismiss, `role="alertdialog"` with `aria-labelledby` the generated title id and, when there is a
498
+ message, `aria-describedby` its paragraph). The card (`w-75
499
+ max-w-[calc(100vw-2*var(--spacing-gutter))] bg-surface rounded-card shadow-floating p-6 flex flex-col gap-4`) is an
500
+ `h2.text-heading` title, an optional `text-body text-text-secondary` message and a right-aligned
501
+ `buttons` row (default `[{ label: 'OK' }]`): `cancel`-role renders `text`, `destructive`-role
502
+ `danger`, the first plain button `primary`, further plain ones `tonal`. Unlike every other
503
+ overlay, `Escape` and the hardware back button do not close through the core's
504
+ `dismissOnEscape`/back handling (the service passes `onEscape` and claims the back button itself):
505
+ they
506
+ resolve with the cancel button's label when `buttons` has one, and are otherwise claimed and
507
+ ignored, since an alert with no cancel button can only be answered by tapping a button. Focus
508
+ lands on the first button — the core's first-tabbable search, since the title and message hold no
509
+ focusable content. `provideBfShell()` now also provides `BfAlertDialogService`.