@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,474 @@
1
+ # bf-content
2
+
3
+ Page's single scroll container that keeps focused fields clear of the soft keyboard
4
+
5
+ `BfContentComponent` from `@bitforgehq/ui/mobile` · Navigation · since 0.2.0
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, signal, viewChild } from '@angular/core';
11
+ import { BfButtonComponent, BfListComponent, BfListItemComponent } from '@bitforgehq/ui';
12
+ import { BfContentComponent, BfHeaderComponent, BfPageComponent } from '@bitforgehq/ui/mobile';
13
+
14
+ /** `bf-content` showcase: a long scroller reporting its offset, with scroll-to-top buttons. */
15
+ @Component({
16
+ selector: 'app-content-showcase',
17
+ imports: [
18
+ BfPageComponent,
19
+ BfHeaderComponent,
20
+ BfContentComponent,
21
+ BfButtonComponent,
22
+ BfListComponent,
23
+ BfListItemComponent,
24
+ ],
25
+ template: `
26
+ <bf-page data-showcase="content">
27
+ <bf-header title="Content" [largeTitle]="false">
28
+ <bf-button bfSlot="end" variant="text" size="sm" (clicked)="toTop()">Top</bf-button>
29
+ </bf-header>
30
+ <bf-content [scrollEvents]="true" (scrolled)="scrollTop.set($event.scrollTop)">
31
+ <div class="flex flex-col gap-4 p-4">
32
+ <p class="text-caption text-text-muted">
33
+ The page's only scroller. Scroll down, then tap Top (or the button at the end) to scroll
34
+ back smoothly.
35
+ </p>
36
+ <p data-showcase-result class="text-body">Scrolled {{ scrollTop() }} px</p>
37
+ <bf-list [inset]="false">
38
+ @for (row of rows; track row) {
39
+ <bf-list-item [title]="'Item ' + row" />
40
+ }
41
+ </bf-list>
42
+ <bf-button variant="tonal" (clicked)="toTop()">Back to top</bf-button>
43
+ </div>
44
+ </bf-content>
45
+ </bf-page>
46
+ `,
47
+ })
48
+ export class ContentShowcasePage {
49
+ private readonly content = viewChild.required(BfContentComponent);
50
+
51
+ protected readonly scrollTop = signal(0);
52
+ protected readonly rows = Array.from({ length: 40 }, (_, index) => index + 1);
53
+
54
+ toTop(): void {
55
+ this.content().scrollToTop();
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Overview
61
+
62
+ The page's single scroll container: fills the rest of `bf-page` under the header, keeps the
63
+ focused text control clear of the soft keyboard (`bfKeyboardAssist`), pads for the horizontal
64
+ safe areas, for the floating tab bar (`--bf-tab-bar-inset`), for a `translucent` header drawn
65
+ over it (`--bf-header-height`, from `BfPageContext`) and at the bottom for the height a solid
66
+ `bf-header` has collapsed by (`--bf-header-collapse`, written by the header, so the scroll range
67
+ stays the same while the large title collapses), and contains overscroll so the document
68
+ never scrolls. On iOS (`ios.scrollEnabled: false`) it is the only thing that scrolls. The
69
+ projected content sits in a block body inside the scroller: in iOS mode the body is 1 px taller
70
+ than the scroller, so WebKit rubber-bands even a short page; in Material mode the scroller gets
71
+ `bfOverscrollStretch`, the Android edge stretch the WebView does not draw itself, except in iOS
72
+ WebKit (Safari or a home-screen app, platform `web`), which rubber-bands natively. Both follow the
73
+ engine (`BfPlatformService.mode()`), not `provideBfShell({ mode })`. In the page a
74
+ `bf-stack-outlet` shows, the scroller is the document's `main` landmark (`role="main"`; not in a
75
+ `bf-modal`, see `BfPageContext.mainLandmark`). While it overflows and holds nothing focusable,
76
+ it is a tab stop (`tabindex="0"`), so a keyboard can scroll it in Safari too (axe
77
+ `scrollable-region-focusable`); otherwise Tab reaches its controls and the arrow keys scroll it.
78
+
79
+ The page's single scroll container. `bf-content` fills the rest of a `bf-page` under its header,
80
+ scrolls vertically with `overscroll-behavior: contain`, pads for the left and right safe areas and
81
+ for the floating tab bar, and keeps the focused text control clear of the soft keyboard. On iOS the
82
+ app runs with `ios.scrollEnabled: false`, so the document itself never scrolls: `bf-content` is the
83
+ only scroller, and everything that should scroll — lists, forms, pull-to-refresh — goes inside it.
84
+
85
+ ## When to use
86
+
87
+ Use `bf-content` as the one scroll container of every `bf-page` in the Capacitor shell, after the page's [bf-header](https://bitforge-ui.web.app/components/header). Use the document's own scrolling instead on a desktop web page, and the [bfKeyboardAssist](https://bitforge-ui.web.app/api/bf-keyboard-assist-directive) directive only for a custom scroller that is not a `bf-content`.
88
+
89
+ ## Basic usage
90
+
91
+ ```ts
92
+ import { Component } from '@angular/core';
93
+ import { BfListComponent, BfListItemComponent } from '@bitforgehq/ui';
94
+ import { BfContentComponent, BfPageComponent } from '@bitforgehq/ui/mobile';
95
+
96
+ /**
97
+ * A page's scrolling content.
98
+ *
99
+ * `bf-content` is the page's scroll container, clear of the header and the safe areas.
100
+ *
101
+ * @preview none
102
+ */
103
+ @Component({
104
+ selector: 'app-example-content-basic',
105
+ imports: [BfContentComponent, BfListComponent, BfListItemComponent, BfPageComponent],
106
+ template: `
107
+ <bf-page>
108
+ <bf-content>
109
+ <bf-list>
110
+ <bf-list-item title="Inbox" />
111
+ <bf-list-item title="Archive" />
112
+ </bf-list>
113
+ </bf-content>
114
+ </bf-page>
115
+ `,
116
+ })
117
+ export class ContentBasicExampleComponent {}
118
+ ```
119
+
120
+ ## The only scroller
121
+
122
+ Use one `bf-content` per page and put every scrollable part of the page inside it. The host is a
123
+ `flex-1 min-h-0` child of `bf-page`'s column, so it takes the height left under a header and its
124
+ inner scroller (`h-full overflow-y-auto`) does the scrolling. With the required
125
+ `capacitor.config` (`ios: { scrollEnabled: false }`), content placed outside `bf-content` does not
126
+ scroll at all on iOS. A horizontal row inside it (a chips row or a carousel with
127
+ `overflow-x-auto`) pans on its own; it needs no directive.
128
+
129
+ ## Fullscreen
130
+
131
+ Without a header above it, set `fullscreen` so the content starts below the status bar
132
+ (`pt-safe-top`) instead of under it. Inside a page-sheet `bf-modal`, whose card already sits below
133
+ the status bar (`BfPageContext.safeAreaTop` false), `fullscreen` adds no top padding.
134
+
135
+ ```ts
136
+ import { Component } from '@angular/core';
137
+ import { BfContentComponent, BfPageComponent } from '@bitforgehq/ui/mobile';
138
+
139
+ /**
140
+ * Content that runs under the status bar.
141
+ *
142
+ * `fullscreen` drops the top safe-area padding, for a page without a header.
143
+ *
144
+ * @preview none
145
+ */
146
+ @Component({
147
+ selector: 'app-example-content-fullscreen',
148
+ imports: [BfContentComponent, BfPageComponent],
149
+ template: `
150
+ <bf-page>
151
+ <bf-content [fullscreen]="true">
152
+ <h1 data-bf-page-title class="text-display">Today</h1>
153
+ </bf-content>
154
+ </bf-page>
155
+ `,
156
+ })
157
+ export class ContentFullscreenExampleComponent {}
158
+ ```
159
+
160
+ ## Keyboard assist
161
+
162
+ `bf-content` applies `bfKeyboardAssist` to its scroller. When the soft keyboard opens it pads the
163
+ scroller by the part the keyboard covers and scrolls the focused `input`, `textarea` or
164
+ `[contenteditable]` until it sits 24 px above the keyboard, after the engine's own focus scroll,
165
+ with one correction once the keyboard has settled. Focus moving to the next field while the
166
+ keyboard is open reveals that one too. Turn it off with `keyboardAssist` for a screen that manages
167
+ the keyboard itself, such as a chat with a composer pinned above the keyboard.
168
+
169
+ ```ts
170
+ import { Component, signal } from '@angular/core';
171
+ import { BfButtonComponent, BfTextareaComponent } from '@bitforgehq/ui';
172
+ import { BfContentComponent, BfPageComponent } from '@bitforgehq/ui/mobile';
173
+
174
+ /** A chat that keeps its composer above the keyboard itself. */
175
+ @Component({
176
+ selector: 'app-chat-thread',
177
+ imports: [BfButtonComponent, BfTextareaComponent],
178
+ template: `
179
+ <div class="flex h-full flex-col">
180
+ <ul class="flex flex-1 flex-col gap-2 p-4">
181
+ @for (message of messages(); track $index) {
182
+ <li class="bg-surface rounded-card p-3">{{ message }}</li>
183
+ }
184
+ </ul>
185
+ <div class="pb-safe-bottom bg-bg sticky bottom-0 flex items-end gap-2 p-2">
186
+ <bf-textarea class="flex-1" ariaLabel="Message" [rows]="1" [(value)]="draft" />
187
+ <bf-button (clicked)="send()">Send</bf-button>
188
+ </div>
189
+ </div>
190
+ `,
191
+ })
192
+ export class ChatThreadComponent {
193
+ protected readonly messages = signal(['Is the deck ready?']);
194
+ protected readonly draft = signal('');
195
+
196
+ protected send(): void {
197
+ this.messages.update((messages) => [...messages, this.draft()]);
198
+ this.draft.set('');
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Content without the keyboard assist.
204
+ *
205
+ * `[keyboardAssist]="false"` leaves the keyboard to a screen that handles it itself, such as a
206
+ * chat with a composer pinned above the keyboard.
207
+ *
208
+ * @preview none
209
+ */
210
+ @Component({
211
+ selector: 'app-example-content-keyboard',
212
+ imports: [BfContentComponent, BfPageComponent, ChatThreadComponent],
213
+ template: `
214
+ <bf-page>
215
+ <bf-content [keyboardAssist]="false">
216
+ <app-chat-thread />
217
+ </bf-content>
218
+ </bf-page>
219
+ `,
220
+ })
221
+ export class ContentKeyboardExampleComponent {}
222
+ ```
223
+
224
+ ## Overscroll
225
+
226
+ The projected content sits in a block body inside the scroller, so an overscroll effect can move
227
+ the body without touching the scroller's own padding.
228
+
229
+ - **iOS**: WebKit rubber-bands a scroller only when its content overflows. In iOS mode the body is
230
+ at least 1 px taller than the scroller (`min-h-[calc(100%+1px)]`), so a short page bounces like a
231
+ native list too; the 1 px of scroll is imperceptible.
232
+ - **Android**: the WebView draws no overscroll effect for an inner scroller. In Material mode the
233
+ scroller carries `bfOverscrollStretch`, which stretches the body at the top or bottom edge under
234
+ the finger and springs it back. With an enabled `bf-refresher` on the page, only the bottom edge
235
+ stretches; the top pull belongs to the refresher.
236
+ - **iOS Safari and home-screen apps**: Capacitor reports platform `web` there, so the mode is
237
+ Material, but WebKit rubber-bands natively. `bf-content` detects iOS WebKit (an iPhone, iPad or
238
+ iPod user agent, or an iPad that reports `MacIntel` with touch points) and leaves the stretch
239
+ off, so it never stacks on the bounce.
240
+
241
+ The overscroll follows the engine (`BfPlatformService.mode()`, iOS only on native iOS), not
242
+ `provideBfShell({ mode })`: forcing `mode: 'ios'` on Android keeps the stretch and the Material pull,
243
+ and forcing `mode: 'md'` on iOS keeps the bounce. The bounce is WebKit's, and the refresher's iOS
244
+ mode reads it from a negative `scrollTop`; Chrome never reports a negative `scrollTop`, so an
245
+ Android WebView in the iOS look would get neither a bounce nor a working pull-to-refresh.
246
+
247
+ ## Tab bar inset
248
+
249
+ Inside `bf-tabs`, the floating tab bar sets `--bf-tab-bar-inset` and `bf-content` pads its bottom
250
+ by it (`0px` outside tabs), so the last row scrolls clear of the bar.
251
+
252
+ ## Large-title compensation
253
+
254
+ A solid `bf-header` above the scroller shrinks as its large title collapses, and the scroller grows
255
+ by the same height. The header writes that height as `--bf-header-collapse` on the scroller, and
256
+ `bf-content` adds it to the bottom padding
257
+ (`pb-[calc(var(--bf-tab-bar-inset,0px)+var(--bf-header-collapse,0px))]`), so the scroll range stays
258
+ the same through the collapse and a page that scrolls just over the 56 px collapse range never
259
+ flips between expanded and collapsed. While the soft keyboard is up, the keyboard assist's inline
260
+ bottom padding takes over.
261
+
262
+ ## Scroll events and scrolling
263
+
264
+ `scrolled` emits a `BfContentScrollEvent` (`{ scrollTop }`) from a passive listener, but only while `scrollEvents` is true, so
265
+ plain pages add no scroll listener and no change detection while scrolling. `scrollToTop()` and
266
+ `scrollToPoint(x, y)` scroll smoothly by default; `scrollElement` returns the scroll container.
267
+
268
+ ```ts
269
+ import { Component, signal } from '@angular/core';
270
+ import { BfButtonComponent, BfListComponent, BfListItemComponent } from '@bitforgehq/ui';
271
+ import { BfContentComponent, BfPageComponent } from '@bitforgehq/ui/mobile';
272
+
273
+ /**
274
+ * Scroll events and scrolling to the top.
275
+ *
276
+ * `scrollEvents` turns on `scrolled`; the template reference calls `scrollToTop()`.
277
+ *
278
+ * @preview none
279
+ */
280
+ @Component({
281
+ selector: 'app-example-content-scroll',
282
+ imports: [
283
+ BfButtonComponent,
284
+ BfContentComponent,
285
+ BfListComponent,
286
+ BfListItemComponent,
287
+ BfPageComponent,
288
+ ],
289
+ template: `
290
+ <bf-page>
291
+ <bf-content #content [scrollEvents]="true" (scrolled)="onScroll($event.scrollTop)">
292
+ <bf-list>
293
+ @for (row of rows; track row) {
294
+ <bf-list-item [title]="'Task ' + row" />
295
+ }
296
+ </bf-list>
297
+ @if (scrolledDown()) {
298
+ <bf-button (clicked)="content.scrollToTop()">Back to top</bf-button>
299
+ }
300
+ </bf-content>
301
+ </bf-page>
302
+ `,
303
+ })
304
+ export class ContentScrollExampleComponent {
305
+ protected readonly rows = Array.from({ length: 40 }, (_, i) => i + 1);
306
+ protected readonly scrolledDown = signal(false);
307
+
308
+ protected onScroll(scrollTop: number): void {
309
+ this.scrolledDown.set(scrollTop > 400);
310
+ }
311
+ }
312
+ ```
313
+
314
+ ## Desktop
315
+
316
+ `bf-content` is part of the Capacitor shell (`provideBfShell()`). A desktop web app lets the document scroll: put the page in your own layout (a `main` that scrolls with the window) and leave `bf-page`, `bf-content` and `bf-stack-outlet` out. Inside the shell on a desktop browser, wheel, trackpad and keyboard scrolling are the browser's own, the keyboard assist adds nothing (no soft keyboard), and the Android stretch never starts (it follows touch only). Safari does not let the keyboard reach a scroll container with nothing focusable in it, so while the content overflows and holds no control, the scroller is a tab stop itself: <kbd>Tab</kbd> focuses it (with the focus ring) and the arrow keys, <kbd>Page Down</kbd> and <kbd>Space</kbd> scroll it. With a control inside, Tab reaches the control instead and the scroller stays out of the Tab order.
317
+
318
+ ## Theming
319
+
320
+ - Background: none of its own; it shows `bg` from the surrounding `bf-page`.
321
+ - Spacing: `--spacing-safe-left` / `--spacing-safe-right` (horizontal safe areas),
322
+ `--spacing-safe-top` when `fullscreen` (not inside a page-sheet modal), and `--bf-tab-bar-inset` plus `--bf-header-collapse`
323
+ (bottom, set by the tab bar and by a solid `bf-header` while its large title is collapsed, both
324
+ default `0px`).
325
+ - Focus ring (only while the scroller is a tab stop): `ring` at `ring-4`, inset.
326
+ - Layout: host `block min-h-0 flex-1`; scroller `h-full overflow-y-auto overscroll-contain
327
+ touch-pan-y`; inside it a plain block body wraps the projected content (`min-h-[calc(100%+1px)]`
328
+ in iOS mode). Add your own padding on the content inside it. The body's height is `auto`, so a
329
+ child can no longer fill the page with `h-full`: percentage heights (`h-full`, `min-h-full`, and
330
+ the `flex-1` of a wrapper sized with them) resolve to nothing there. Size such a child from its
331
+ content or the viewport on your own wrapper (a centred empty state: `min-h-[60dvh]`), not with
332
+ `h-full`.
333
+ - Page gutter: pad page content with the 16 px screen gutter (`px-gutter` or `p-4`), the inset the
334
+ header's large title, toasts and alert dialogs use, so text lines up down the screen.
335
+
336
+ ## Accessibility
337
+
338
+ In the page a `bf-stack-outlet` shows, the scroller is the document's `main` landmark
339
+ (`role="main"`, from `BfPageContext.mainLandmark`), so there is exactly one `main` at any time:
340
+ parked pages are `aria-hidden`, and the content of a `bf-modal` (or of a `bf-page` outside an
341
+ outlet, such as a docs illustration) is no landmark. While the scroller overflows and holds nothing
342
+ focusable it is a tab stop (`tabindex="0"`, axe `scrollable-region-focusable`), so a keyboard can
343
+ scroll it in Safari; once a control appears inside, it leaves the Tab order again. Controls that
344
+ are `hidden`, inside an `inert` or `hidden` subtree or not rendered (`display: none`) do not count,
345
+ and the 1 px the iOS body adds for the rubber band is not overflow. The page's
346
+ heading comes from its content. The keyboard assist keeps the focused control visible above the soft keyboard, so what is
347
+ typed is always on screen, and it never moves focus. `overscroll-contain` keeps a scroll gesture
348
+ from chaining into the document or a parent scroller, and `touch-pan-y` leaves horizontal drags to
349
+ the edge swipe-back. The overscroll is pure feel, with no behaviour of its own: on iOS every page
350
+ bounces, short ones too, and on Android the content stretches at both edges; neither moves focus,
351
+ changes what is read, or prevents a touch, and the Android stretch springs back instantly under
352
+ `prefers-reduced-motion`.
353
+
354
+ ### Keyboard
355
+
356
+ | Key | Where | Action |
357
+ | ------------------------------------------------------------ | ----------------------------------------- | ------------------------------------- |
358
+ | <kbd>Tab</kbd> | Content with nothing focusable, scrolling | Focuses the scroller |
359
+ | <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Scroller or a control inside it | Scrolls (the browser's own) |
360
+ | <kbd>Page Up</kbd> / <kbd>Page Down</kbd> / <kbd>Space</kbd> | Scroller | Scrolls by a page (the browser's own) |
361
+
362
+ ## Inputs
363
+
364
+ | Name | Type | Default | Description |
365
+ | ---------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------- |
366
+ | `fullscreen` | `boolean` | `false` | Extends the content under the status bar: adds the top safe-area padding (no header above). |
367
+ | `keyboardAssist` | `boolean` | `true` | Keeps the focused text control clear of the soft keyboard; false for screens that handle it themselves. |
368
+ | `scrollEvents` | `boolean` | `false` | Emits `scrolled` on every scroll; off by default so plain pages add no listener. |
369
+
370
+ ## Outputs
371
+
372
+ | Name | Payload | Description |
373
+ | ---------- | ---------------------- | ----------------------------------------------------------------------------- |
374
+ | `scrolled` | `BfContentScrollEvent` | The scroller's `scrollTop` on each scroll, only while `scrollEvents` is true. |
375
+
376
+ ## Slots
377
+
378
+ - `default`: The page content, in the scroll container.
379
+
380
+ ## Methods
381
+
382
+ ### `scrollToTop(behavior?: ScrollBehavior): void`
383
+
384
+ Scrolls back to the top (e.g. on a second tap of the active tab).
385
+
386
+ | Parameter | Type | Description |
387
+ | ----------- | ---------------- | ------------------------------------ |
388
+ | `behavior?` | `ScrollBehavior` | `smooth` (the default) or `instant`. |
389
+
390
+ ### `scrollToPoint(x: number, y: number, behavior?: ScrollBehavior): void`
391
+
392
+ Scrolls to the given offset in px.
393
+
394
+ | Parameter | Type | Description |
395
+ | ----------- | ---------------- | ------------------------------------ |
396
+ | `x` | `number` | The horizontal offset (px). |
397
+ | `y` | `number` | The vertical offset (px). |
398
+ | `behavior?` | `ScrollBehavior` | `smooth` (the default) or `instant`. |
399
+
400
+ ## Notes
401
+
402
+ ### Do
403
+
404
+ - Use exactly one `bf-content` per `bf-page`, after the header.
405
+ - Pad the page content with the 16 px gutter (`px-gutter`), so it aligns with the large title.
406
+ - Keep `keyboardAssist` on for any page with text fields.
407
+ - Set `scrollEvents` only when something listens to `scrolled`.
408
+
409
+ ### Don't
410
+
411
+ - Don't make the document or another wrapper scroll; on iOS it cannot.
412
+ - Don't nest a second vertical scroller inside `bf-content` for the page's main content.
413
+ - Don't add bottom padding for the tab bar yourself; `--bf-tab-bar-inset` already covers it.
414
+
415
+ ## Migration notes
416
+
417
+ ### 0.3.0
418
+
419
+ New exported `BfContentScrollEvent` (`{ scrollTop: number }`), the payload of `scrolled` (was an
420
+ inline object type of the same shape).
421
+
422
+ JSDoc of `content.component.ts` for the API reference: `@slot` docs for `default`; `@param` docs. No
423
+ behaviour change.
424
+
425
+ **Behaviour:** `--bf-header-height` is written in the render phase (`afterRenderEffect`), and the
426
+ scroller registers in `BfPageContext` once after the first render (`afterNextRender`) instead of
427
+ from an `effect`; it is still cleared on destroy. Internal timing only, no API change.
428
+
429
+ `fullscreen` pads the top safe area only while the page's `BfPageContext.safeAreaTop()` is true;
430
+ inside a page-sheet `bf-modal` (false) it adds no top padding (Migration 29).
431
+
432
+ **Behaviour:** in the page a `bf-stack-outlet` shows, the scroller is the `main` landmark
433
+ (`role="main"`, while `BfPageContext.mainLandmark()` is true). While it overflows and holds nothing
434
+ focusable it is a tab stop (`tabindex="0"`, with an inset focus ring), so a keyboard can scroll it
435
+ in Safari and WKWebView (which never focus a scroll container on their own; Chromium does); a
436
+ control appearing inside takes it out of the Tab order again (re-checked per frame on resize and
437
+ content changes, including `hidden` and `inert` toggles). Hidden, inert and unrendered controls do
438
+ not count, and overflow means more than 2 px (the iOS body's 1 px rubber-band extra is not
439
+ overflow). See Migration 36.
440
+
441
+ ### 0.2.0
442
+
443
+ New `bf-content`: the page's single scroll container (host `block min-h-0 flex-1`, scroller
444
+ `h-full overflow-y-auto overscroll-contain touch-pan-y pl-safe-left pr-safe-right` with
445
+ `padding-bottom: var(--bf-tab-bar-inset, 0px)`, plus `pt-safe-top` when `fullscreen`, otherwise
446
+ `padding-top` and `scroll-padding-top` of `var(--bf-header-height, 0px)`, a variable it writes on
447
+ its scroller from `BfPageContext.headerHeight` while a `translucent` header is drawn over it). It applies
448
+ `bfKeyboardAssist` to its scroller (`keyboardAssist`, default `true`), emits `scrolled`
449
+ (`{ scrollTop }`) from a passive listener only while `scrollEvents` is true, offers
450
+ `scrollToTop(behavior = 'smooth')`, `scrollToPoint(x, y, behavior)` and a `scrollElement` getter,
451
+ and registers its scroller in the page's `BfPageContext`.
452
+
453
+ Changed: the projected content now sits in a plain block body inside the scroller. In iOS mode
454
+ (`BfPlatformService.mode()`) the body is `min-h-[calc(100%+1px)]`, so the scroller always overflows
455
+ by 1 px and WebKit rubber-bands short pages too (it only bounces an overflowing scroller); in
456
+ Material mode the scroller carries `bfOverscrollStretch`, the Android edge stretch the WebView does
457
+ not draw. `bf-refresher` without a page context now tracks its nearest scrolling ancestor (past
458
+ the body wrapper) instead of its parent element. The stretch stays off in iOS WebKit (Safari or a
459
+ home-screen app, where Capacitor reports platform `web` and the mode is Material, detected from an
460
+ `iPhone`/`iPad`/`iPod` user agent or `MacIntel` with more than one touch point), which rubber-bands
461
+ natively, so the two never stack.
462
+
463
+ Migration: the body's height is `auto`, so children that filled the scroller with `h-full` (a
464
+ centred empty state) now collapse, and `min-h-full` or a `flex-1` inside a wrapper sized that way
465
+ collapses too. Size such a child from its content or the viewport on your own wrapper
466
+ (`min-h-[60dvh]`), not with `h-full`. The overscroll (the iOS bounce, the Android stretch and the
467
+ refresher's iOS bounce mode) follows the engine (`BfPlatformService.mode()`), not
468
+ `provideBfShell({ mode })`: Chrome never reports a negative `scrollTop`, so an Android app forced
469
+ into the iOS look keeps the stretch and the material pull.
470
+
471
+ Fixed: the scroller's bottom padding also adds `--bf-header-collapse`, the height a solid
472
+ `bf-header` has collapsed by (`pb-[calc(var(--bf-tab-bar-inset,0px)+var(--bf-header-collapse,0px))]`,
473
+ replacing `pb-(--bf-tab-bar-inset,0px)`), so the scroll range stays constant while the large title
474
+ collapses. The keyboard assist's inline `padding-bottom` still overrides it while the keyboard is up.