@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,408 @@
1
+ # bf-tabs
2
+
3
+ Tabs page with one navigation stack per tab, wiring the tab bar to the router
4
+
5
+ `BfTabsComponent` from `@bitforgehq/ui/mobile` · Navigation · since 0.2.0
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, computed, inject } from '@angular/core';
11
+ import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
12
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
13
+ import { BfStackOutletComponent, BfTabsComponent } from '@bitforgehq/ui/mobile';
14
+ import { Folder, Inbox, Sun, User } from 'lucide';
15
+ import { InboxStateService } from '../../services/state/inbox.state.service';
16
+
17
+ /**
18
+ * The tabbed app: one navigation stack per tab under `/tabs/<tab>`, and the floating tab bar with
19
+ * the Inbox unread badge.
20
+ */
21
+ @Component({
22
+ selector: 'app-tabs',
23
+ host: { class: 'block h-full' },
24
+ imports: [
25
+ BfTabsComponent,
26
+ BfStackOutletComponent,
27
+ BfTabBarComponent,
28
+ BfTabComponent,
29
+ BfIconComponent,
30
+ ],
31
+ template: `
32
+ <bf-tabs tabsPath="tabs">
33
+ <bf-stack-outlet />
34
+ <bf-tab-bar label="Main">
35
+ <bf-tab id="today" label="Today"><bf-icon [icon]="sun" /></bf-tab>
36
+ <bf-tab id="projects" label="Projects"><bf-icon [icon]="folder" /></bf-tab>
37
+ <bf-tab id="inbox" label="Inbox" [badge]="inboxBadge()"><bf-icon [icon]="inbox" /></bf-tab>
38
+ <bf-tab id="me" label="Me"><bf-icon [icon]="user" /></bf-tab>
39
+ </bf-tab-bar>
40
+ </bf-tabs>
41
+ `,
42
+ })
43
+ export class TabsPage {
44
+ private readonly inboxState = inject(InboxStateService);
45
+
46
+ /** Unread messages on the Inbox tab; no badge at zero. */
47
+ protected readonly inboxBadge = computed(() => this.inboxState.unread() || null);
48
+ protected readonly sun = Sun;
49
+ protected readonly folder = Folder;
50
+ protected readonly inbox = Inbox;
51
+ protected readonly user = User;
52
+ }
53
+ ```
54
+
55
+ ## Overview
56
+
57
+ The tabs page of a mobile app: one navigation stack per tab. It wraps a `bf-stack-outlet`, which
58
+ keeps a separate stack for every tab (keyed by the URL segment after `tabsPath`), and a
59
+ `bf-tab-bar`, which it drives through `BF_TAB_BAR_CONTROLLER`: the bar shows the tab of the
60
+ in-flight navigation's target, else of the current URL, and hides while the keyboard is open,
61
+ without anything writing into it. Selecting another tab brings
62
+ that tab back as it was left; re-selecting the active tab pops its stack to the root, or scrolls
63
+ its content to the top when it is already there. The bar hides while the soft keyboard is open
64
+ or about to open (a focused text control), and `--bf-tab-bar-inset` on the host tells
65
+ `bf-content` how much to pad its bottom (the bar's measured height, a `ResizeObserver` on its
66
+ `nav`, plus its bottom offset and 8 px; 64 px for the height until it is measured); the same value is mirrored on `<html>` as
67
+ `--bf-tab-bar-inset-global` for overlays (the toast), which render outside the host. It is removed when the tabs are destroyed.
68
+ It also provides `BF_TAB_LINKS`, so the projected bar is a `nav` of links to each tab's root URL
69
+ (`aria-current` on the active one: `page` at the tab's root, `true` on a child page of it)
70
+ instead of a `tablist`.
71
+
72
+ The tabs page of a mobile app, with one navigation stack per tab. `bf-tabs` wraps a
73
+ `bf-stack-outlet` and a `bf-tab-bar`: the outlet keeps a separate stack for every tab, keyed by the
74
+ URL segment after the tabs route, so switching tabs parks the current tab's whole stack (depth,
75
+ scroll and state kept) and brings the other one back exactly as it was left. The bar derives its
76
+ selection from the router (through `BF_TAB_BAR_CONTROLLER`: the target of a navigation in flight,
77
+ else the current tab route), the bar hides while the soft keyboard is open, and
78
+ `--bf-tab-bar-inset` tells `bf-content` how far to pad its bottom.
79
+
80
+ ## When to use
81
+
82
+ Use `bf-tabs` for the tab root of a Capacitor app: one navigation stack per tab behind a floating [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar). Use [bf-stack-outlet](https://bitforge-ui.web.app/components/stack-outlet) instead for an app with one stack and no tabs, and a sidebar of links on desktop.
83
+
84
+ ## Basic usage
85
+
86
+ The tabs page is a route with one child route per tab page; deeper pages of a tab are more child
87
+ routes that start with the tab's segment. Every `bf-tab`'s `id` is that segment.
88
+
89
+ ```ts
90
+ import { Component } from '@angular/core';
91
+ import { type Routes } from '@angular/router';
92
+ import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
93
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
94
+ import {
95
+ BfContentComponent,
96
+ BfHeaderComponent,
97
+ BfPageComponent,
98
+ BfStackOutletComponent,
99
+ BfTabsComponent,
100
+ } from '@bitforgehq/ui/mobile';
101
+ import { Folder, Sun, User } from 'lucide';
102
+
103
+ /** A tab's root page (the three tabs share it here). */
104
+ @Component({
105
+ selector: 'app-tab-root-page',
106
+ imports: [BfContentComponent, BfHeaderComponent, BfPageComponent],
107
+ template: `
108
+ <bf-page>
109
+ <bf-header title="Tab" />
110
+ <bf-content><p class="p-4">A tab's first page.</p></bf-content>
111
+ </bf-page>
112
+ `,
113
+ })
114
+ export class TabRootPage {}
115
+
116
+ /**
117
+ * Tabs bound to the router.
118
+ *
119
+ * Each tab is a child route of `tabs` with a stack of its own; the tab bar inside `bf-tabs`
120
+ * becomes links to the tabs' roots.
121
+ *
122
+ * @preview none
123
+ */
124
+ @Component({
125
+ selector: 'app-example-tabs-basic',
126
+ imports: [
127
+ BfIconComponent,
128
+ BfStackOutletComponent,
129
+ BfTabBarComponent,
130
+ BfTabComponent,
131
+ BfTabsComponent,
132
+ ],
133
+ template: `
134
+ <bf-tabs tabsPath="tabs">
135
+ <bf-stack-outlet />
136
+ <bf-tab-bar>
137
+ <bf-tab id="today" label="Today"><bf-icon [icon]="sun" /></bf-tab>
138
+ <bf-tab id="projects" label="Projects"><bf-icon [icon]="folder" /></bf-tab>
139
+ <bf-tab id="me" label="Me"><bf-icon [icon]="user" /></bf-tab>
140
+ </bf-tab-bar>
141
+ </bf-tabs>
142
+ `,
143
+ })
144
+ export class TabsBasicExampleComponent {
145
+ protected readonly sun = Sun;
146
+ protected readonly folder = Folder;
147
+ protected readonly user = User;
148
+ }
149
+
150
+ /** The tabs route: one child path per tab, plus the pages pushed inside a tab. */
151
+ export const routes: Routes = [
152
+ {
153
+ path: 'tabs',
154
+ component: TabsBasicExampleComponent,
155
+ children: [
156
+ { path: 'today', component: TabRootPage },
157
+ { path: 'today/task/:id', component: TabRootPage },
158
+ { path: 'projects', component: TabRootPage },
159
+ { path: 'me', component: TabRootPage },
160
+ { path: '', redirectTo: 'today', pathMatch: 'full' },
161
+ ],
162
+ },
163
+ ];
164
+ ```
165
+
166
+ `tabsPath` is the route path of the tabs page (`'tabs'` above, or `'app/tabs'` for a nested one).
167
+ Navigate inside a tab with `BfNavController.forward()` as anywhere else: `/tabs/today/task/4`
168
+ pushes onto the Today stack. An empty `tabsPath` (`tabsPath=""`) mounts the tabs page at the root
169
+ URL — the tab key is the first URL segment (`/today`, `/me`) with no `tabs` prefix; give the tabs
170
+ route `path: ''` with the same shape of children.
171
+
172
+ ## Switching and re-selecting tabs
173
+
174
+ Selecting another tab (a plain click on its link) navigates to where that tab was left (the URL
175
+ of the top page of its stack, or `/tabs/<id>` the first time) and, once that navigation has succeeded, plays
176
+ `BfHapticsService.selection()` when `hapticsOnTabChange` is on in `provideBfShell()` (a switch a
177
+ guard refuses plays nothing). A tab switch has no transition and no swipe-back;
178
+ `BfNavController.depth`, `canGoBack` and `back()` refer to the active tab's stack while the tabs
179
+ page is shown, so the back button pops within the tab. A page pushed on an outer outlet above the
180
+ tabs page (`/gallery`) parks the tabs page with every tab stack intact until it is popped. Selecting the tab that is already active pops its stack back to
181
+ its existing root page (one history step back, so the root keeps its scroll and state) when it is
182
+ deeper than one page, or scrolls its `bf-content` back to the top when it is already at the root.
183
+
184
+ ## Soft keyboard
185
+
186
+ While `BfKeyboardService.visible()` or `BfKeyboardService.imminent()` is true the bar slides down
187
+ out of view (the projected `bf-tab-bar` reads `hidden()` from `BF_TAB_BAR_CONTROLLER`): a 200 ms `translate`/`opacity`
188
+ transition on iOS, instantly on Android (where the WebView resizes under the keyboard) and on the
189
+ web. `imminent()` turns true on `focusin` of a text control, before any keyboard event: on Android
190
+ the WebView shrinks before the plugin's will-show, and a bar hidden only then would first be lifted
191
+ above the keyboard. It comes back when the keyboard hides or the control loses focus, and 2.5 s
192
+ after the focus when no keyboard shows at all (a hardware keyboard, such as an iPad Magic Keyboard,
193
+ fires no keyboard event).
194
+
195
+ ## Desktop
196
+
197
+ `bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app put the top-level destinations in a sidebar or a top `nav` of links with `routerLinkActive`, and a plain `router-outlet`. Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
198
+
199
+ ## Theming
200
+
201
+ - `--bf-tab-bar-inset` on the `bf-tabs` host:
202
+ `calc(<height>px + 8px + max(var(--spacing-safe-bottom), 12px) + 8px)` (the bar's measured
203
+ height, its bottom offset of 8 px above the safe area with a 12 px floor, and 8 px of room above
204
+ the bar: 114 px on an iPhone with a home indicator at the default text size, 104 px with Android
205
+ gesture navigation, 92 px without an inset) while a bar is present and visible, `0px` while it is
206
+ hidden or absent. The height is measured with a `ResizeObserver` on the projected bar's `nav`, so
207
+ the inset follows a bar that grows with large text (a two-line label at Android font scale 2.0,
208
+ iOS Dynamic Type) or slims down at compact heights (phone landscape); until the first
209
+ measurement it is the bar's 64 px minimum. The observer is disconnected with the tabs. `bf-content`
210
+ pads its scroller's bottom by it; use it for anything else that must clear the bar.
211
+ - `--bf-tab-bar-inset-global` on `<html>`: the same value, for overlays (the toast), which render in
212
+ the CDK overlay container outside `bf-tabs`; removed when the tabs are destroyed. Only overlays
213
+ read it — a page pushed outside `bf-tabs` keeps no bottom padding.
214
+ - The bar itself keeps the `bf-tab-bar` tokens (`surface`, `shadow-floating`, `primary-soft`).
215
+ - The host is `display: block; height: 100%` around a `relative flex h-full flex-col` column; give
216
+ the tabs page a height (it fills the outlet frame it is routed into).
217
+
218
+ ## Accessibility
219
+
220
+ `bf-tabs` provides `BF_TAB_LINKS`, so its bar is a navigation landmark of links, not a `tablist`:
221
+ the tabs switch routes, not in-page panels, which is what the
222
+ [WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/) is for (the
223
+ [tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) assumes tab panels in the same
224
+ page). The bar is `nav` > a plain group > one `<a href>` per tab, pointing at the tab's root URL
225
+ (`/tabs/<id>`, through the app's `LocationStrategy`, so the base href is included), with
226
+ `aria-current` on the active tab: `page` while its root page is shown, `true` on a child page of it
227
+ (the tab is then the current section, not the current page). Tab and Shift+Tab move between the
228
+ tabs, Enter follows one, and the arrow keys do nothing; Ctrl/Cmd-click or a middle click opens the tab's root in a new
229
+ browser tab. A plain click is not followed as a link: the bar turns it into a selection, so
230
+ `bf-tabs` still brings the tab back where it was left, plays its haptic and pops to the root on
231
+ reselect. A standalone `bf-tab-bar` outside `bf-tabs` keeps its `tablist` semantics. While the
232
+ keyboard hides it, the bar's `nav` is `aria-hidden="true"` and `inert`, so it can be neither
233
+ focused nor read. Each tab's
234
+ stack follows `bf-stack-outlet`'s rules: parked stacks are `visibility: hidden`, `inert` and
235
+ `aria-hidden`, and after a switch focus moves to the restored page's title (`data-bf-page-title`).
236
+
237
+ **Safari and iOS WKWebView (the Capacitor iOS app) do not move Tab to links by default.** With a
238
+ hardware keyboard (an iPad with a Magic Keyboard, a Mac), Tab skips the tab bar's links unless the
239
+ user turns on "Press Tab to highlight each item" (Safari settings on macOS; on iPadOS the
240
+ keyboard's Full Keyboard Access); Option+Tab reaches them otherwise. VoiceOver and Full Keyboard
241
+ Access navigation are unaffected, and Chromium-based browsers and Android WebView tab to links as
242
+ usual. The old `button role="tab"` bar was reachable with plain Tab in WebKit; the link bar trades
243
+ that for real URLs, opening a tab in a new browser tab and `aria-current`.
244
+
245
+ Only the `bf-tab-bar` placed directly inside `<bf-tabs>` in the same template becomes links. An
246
+ in-page `bf-tab-bar` inside a tab's page (a routed page) keeps its `tablist`.
247
+
248
+ ### Keyboard
249
+
250
+ In Safari and iOS WKWebView, Tab reaches the links only with "Press Tab to highlight each item" on (Option+Tab otherwise).
251
+
252
+ | Key | Action |
253
+ | ------------------------------------------------ | --------------------------------------------------------- |
254
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between the tab links; a disabled tab is skipped |
255
+ | <kbd>Enter</kbd> | Follows the focused tab's link; the arrow keys do nothing |
256
+
257
+ ## Inputs
258
+
259
+ | Name | Type | Default | Description |
260
+ | ---------- | -------- | -------- | ------------------------------------------------------------------------------------- |
261
+ | `tabsPath` | `string` | required | Route path of the tabs page (e.g. `'tabs'`); the URL segment after it is the tab key. |
262
+
263
+ ## Slots
264
+
265
+ - `default`: The tabs page body: a `bf-stack-outlet` and a `bf-tab-bar`.
266
+
267
+ ## Methods
268
+
269
+ ### `keyOf(url: string): string | null`
270
+
271
+ The tab key of `url`: the primary segment after `tabsPath`. Query, fragment, matrix params and
272
+ auxiliary routes (`(popup:…)`) are ignored.
273
+
274
+ | Parameter | Type | Description |
275
+ | --------- | -------- | ---------------- |
276
+ | `url` | `string` | The URL to read. |
277
+
278
+ ### `href(id: string): string`
279
+
280
+ The URL of tab `id`'s root page (`/tabs/<id>`, with the app's base href), the `href` of its
281
+ link in the bar: a new browser tab opens the tab's root. A plain click still restores the page
282
+ the tab was left on.
283
+
284
+ | Parameter | Type | Description |
285
+ | --------- | -------- | -------------------- |
286
+ | `id` | `string` | The `id` of the tab. |
287
+
288
+ ### `isRoot(id: string): boolean`
289
+
290
+ Whether the current URL is tab `id`'s root page (`…/<tabsPath>/<id>` with nothing after it,
291
+ ignoring query, fragment and auxiliary routes); reactive through the router's signals.
292
+
293
+ | Parameter | Type | Description |
294
+ | --------- | -------- | -------------------- |
295
+ | `id` | `string` | The `id` of the tab. |
296
+
297
+ ### `select(id: string): void`
298
+
299
+ Another tab, selected by the user in the bar: go back to where that tab was left (its stack's
300
+ top page), or its root. The bar shows the target while the navigation is in flight and drops
301
+ back by itself when a guard refuses it.
302
+
303
+ | Parameter | Type | Description |
304
+ | --------- | -------- | ----------------------------- |
305
+ | `id` | `string` | The `id` of the selected tab. |
306
+
307
+ ### `reselect(id: string): void`
308
+
309
+ The active tab again: pop its stack back to its existing root page (a history step), or
310
+ replace the stack with the root when the history position is unknown; at the root, scroll its
311
+ content to the top.
312
+
313
+ | Parameter | Type | Description |
314
+ | --------- | -------- | --------------------------- |
315
+ | `id` | `string` | The `id` of the active tab. |
316
+
317
+ ## Notes
318
+
319
+ ### Do
320
+
321
+ - Give every `bf-tab` the `id` of its route segment, and start every page of a tab with it.
322
+ - Put `bf-content` in every tab page so it pads for the bar.
323
+
324
+ ### Don't
325
+
326
+ - Don't bind `[(active)]` or `(changed)` on the bar inside `bf-tabs`; `bf-tabs` owns both.
327
+ - Don't look for `role="tab"` inside `bf-tabs` in tests or scripts; query the tabs as links
328
+ (`getByRole('link', { name: 'Projects' })`) and the active one by `aria-current` (`page` at the
329
+ tab's root, `true` below it).
330
+ - Don't nest a second `bf-tabs` inside a tab.
331
+
332
+ ## Migration notes
333
+
334
+ ### 0.3.0
335
+
336
+ **Breaking:** (ARIA contract) `bf-tabs` provides `BF_TAB_LINKS`, so the `bf-tab-bar` projected
337
+ directly into it is a `nav` of links with `aria-current` (`page` at the tab's root, `true` on a
338
+ child page) instead of a `tablist` (the tabs switch routes, not in-page panels); an in-page bar on
339
+ a tab's page keeps its `tablist`. New `href(id)`: the tab's root URL, `/<tabsPath>/<id>` through
340
+ `LocationStrategy` (base href included), used as each tab's `href`, so Ctrl/Cmd-click opens a tab
341
+ in a new browser tab; a plain click still restores the tab's last page. New `isRoot(id)`: whether
342
+ the current URL is that root. Tab moves between tabs (in Safari and iOS WKWebView only with "Press
343
+ Tab to highlight each item", else Option+Tab); the arrow keys no longer do (they stopped after one
344
+ step before). See Migration 11.
345
+
346
+ JSDoc of `tab-context.ts` for the API reference: `@summary`/`@since` on the exported symbols;
347
+ `@param` docs. No behaviour change.
348
+
349
+ JSDoc of `tabs.component.ts` for the API reference: `@slot` docs for `default`; `@param` docs. No
350
+ behaviour change.
351
+
352
+ **Behaviour:** the bar is driven through `BF_TAB_BAR_CONTROLLER` instead of written into (F-021).
353
+ `current()` (read-only) is the tab of the in-flight navigation's target after redirects once they
354
+ are applied (`router.currentNavigation()`), else of
355
+ the settled URL (`router.lastSuccessfulNavigation()`), keeping the last tab while neither is under
356
+ `tabsPath`: a clicked tab shows at once and a navigation a guard refuses drops back by itself.
357
+ `hidden()` is `keyboard.visible() || keyboard.imminent()`, `hideTransition()` is true on iOS, and
358
+ `select(id)`/`reselect(id)` are the former `changed`/`reselected` handlers. `tabKey()` and
359
+ `isRoot(id)` read the router's navigation signals; the `router.events` subscription and the three
360
+ effects that wrote into the bar are gone. `--bf-tab-bar-inset` (and `--bf-tab-bar-inset-global` on
361
+ `<html>`) is written in the render phase (`afterRenderEffect`). See Migration 24.
362
+
363
+ **Behaviour:** `--bf-tab-bar-inset` (and `--bf-tab-bar-inset-global`) use the bar's measured height
364
+ instead of the fixed 4 rem (F-051, F-054): an `afterRenderEffect` observes the projected bar's
365
+ `nav` with a `ResizeObserver` and writes
366
+ `calc(<height>px + 8px + max(var(--spacing-safe-bottom), 12px) + 8px)`, or `0px` while the bar is
367
+ hidden; until the first measurement it uses the bar's 64 px minimum. The observer is disconnected on
368
+ destroy. So the page's bottom padding follows a bar grown by large text or slimmed at compact
369
+ heights.
370
+
371
+ ### 0.2.0
372
+
373
+ New `bf-tabs` (`tabsPath` required input): the tabs page with one navigation stack per tab. It
374
+ provides `BF_TAB_CONTEXT` (`tabKey` signal from `NavigationEnd.urlAfterRedirects`, `keyOf(url)` =
375
+ the primary segment after `tabsPath`, parsed with `Router.parseUrl`, so query, fragment, matrix
376
+ params and auxiliary routes are ignored) to the projected `bf-stack-outlet`, sets the
377
+ projected `bf-tab-bar`'s `active` to the tab key, and handles its selections: another tab navigates
378
+ with `nav.forward()` to where that tab was left (`outlet.lastUrlOf(key)`, else `/<tabsPath>/<key>`)
379
+ and, once it has succeeded, `BfHapticsService.selection()` when `hapticsOnTabChange`, putting the
380
+ bar back (without a haptic) if a guard refuses; the active tab again (`tabReselect`) at depth > 1 pops the stack to its existing root page
381
+ (`outlet.popToRoot()`, one `Location.historyGo(-n)` step computed from the outlet's history
382
+ positions, so the root keeps its DOM, scroll and state), falling back to
383
+ `nav.root(['/', tabsPath, key])` when the position is unknown; at depth 1 it scrolls the active
384
+ page's content to the top. It hides the bar while
385
+ `BfKeyboardService.visible()` (`setHidden()`, animated on iOS only, instant on Android and the web) and writes
386
+ `--bf-tab-bar-inset` on its host: `calc(64px + 24px + var(--spacing-safe-bottom))` while a bar is
387
+ present and visible, `0px` otherwise. The same value is mirrored on `<html>` as
388
+ `--bf-tab-bar-inset-global` for overlays, which render outside the host (the toast reads it), and
389
+ removed on destroy; `bf-content` keeps reading `--bf-tab-bar-inset`, so a page outside `bf-tabs`
390
+ gets no bottom padding.
391
+
392
+ Fixed: on Android the bar flashed above the keyboard, because the WebView shrinks (lifting the fixed
393
+ bar) before `keyboardWillShow`. The bar now hides on `BfKeyboardService.visible() ||
394
+ BfKeyboardService.imminent()`, i.e. already on `focusin` of a text control.
395
+
396
+ Fixed: an empty `tabsPath` (the tabs mounted at the root URL) built `['/', '', key]`, a doubled
397
+ slash the router turned into a broken navigation. `onTabChange` and `onTabReselect` now build the
398
+ command array from the non-empty segments of `tabsPath`, so `['/', key]` results when it is empty.
399
+
400
+ Changed: `--bf-tab-bar-inset` (and `--bf-tab-bar-inset-global`) follows the bar's new position:
401
+ `calc(64px + 8px + max(var(--spacing-safe-bottom), 12px) + 8px)` (the bar's height, its offset of
402
+ 8 px above the safe area with a 12 px floor, and 8 px of room above the bar; 114 px on an iPhone
403
+ with a home indicator, was 122 px), replacing `calc(64px + 24px + var(--spacing-safe-bottom))`.
404
+
405
+ Fixed: the inset counts the bar's height in `rem` (`calc(4rem + 8px + max(var(--spacing-safe-bottom),
406
+ 12px) + 8px)`), like the bar's own `h-16`. With iOS Dynamic Type (`BfTextScaleService`) the bar
407
+ grows with the root font size; a fixed 64 px left the end of every page under the bar at the
408
+ accessibility sizes (at AX3 the bar is 150 px tall). Unchanged at the default text size.