@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.
- package/CHANGELOG.md +3665 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/ai/MANIFEST.index.json +383 -0
- package/ai/MANIFEST.json +7528 -0
- package/ai/REFERENCE.index.json +1461 -0
- package/ai/REFERENCE.json +7213 -0
- package/ai/UI_GUIDE.md +1159 -0
- package/ai/components/action-bar.md +209 -0
- package/ai/components/action-sheet.md +526 -0
- package/ai/components/alert-dialog.md +509 -0
- package/ai/components/alert.md +376 -0
- package/ai/components/avatar.md +146 -0
- package/ai/components/back-button.md +286 -0
- package/ai/components/badge.md +171 -0
- package/ai/components/button.md +431 -0
- package/ai/components/card.md +303 -0
- package/ai/components/checkbox.md +418 -0
- package/ai/components/chip-group.md +205 -0
- package/ai/components/chip.md +300 -0
- package/ai/components/content.md +474 -0
- package/ai/components/desktop-toast.md +420 -0
- package/ai/components/dialog.md +962 -0
- package/ai/components/empty-state.md +146 -0
- package/ai/components/field.md +319 -0
- package/ai/components/header.md +809 -0
- package/ai/components/icon-button.md +221 -0
- package/ai/components/icon.md +176 -0
- package/ai/components/input.md +455 -0
- package/ai/components/list-item.md +326 -0
- package/ai/components/list.md +178 -0
- package/ai/components/menu-button.md +377 -0
- package/ai/components/menu.md +661 -0
- package/ai/components/modal.md +613 -0
- package/ai/components/number-input.md +423 -0
- package/ai/components/page.md +299 -0
- package/ai/components/progress.md +215 -0
- package/ai/components/radio-group.md +432 -0
- package/ai/components/refresher.md +377 -0
- package/ai/components/segmented.md +517 -0
- package/ai/components/select.md +510 -0
- package/ai/components/sheet.md +845 -0
- package/ai/components/spinner.md +171 -0
- package/ai/components/stack-outlet.md +661 -0
- package/ai/components/tab-bar.md +427 -0
- package/ai/components/tab.md +295 -0
- package/ai/components/tabs.md +408 -0
- package/ai/components/textarea.md +371 -0
- package/ai/components/toast.md +522 -0
- package/ai/components/toggle.md +387 -0
- package/ai/reference/bf-action-directive.md +21 -0
- package/ai/reference/bf-action-sheet-action-role.md +27 -0
- package/ai/reference/bf-action-sheet-action.md +28 -0
- package/ai/reference/bf-action-sheet-options.md +28 -0
- package/ai/reference/bf-action-sheet-service.md +33 -0
- package/ai/reference/bf-alert-dialog-button-role.md +27 -0
- package/ai/reference/bf-alert-dialog-button.md +27 -0
- package/ai/reference/bf-alert-dialog-options.md +29 -0
- package/ai/reference/bf-alert-dialog-service.md +34 -0
- package/ai/reference/bf-alert-variant.md +28 -0
- package/ai/reference/bf-autocapitalize.md +30 -0
- package/ai/reference/bf-avatar-size.md +27 -0
- package/ai/reference/bf-avatar-variant.md +28 -0
- package/ai/reference/bf-back-at-root.md +28 -0
- package/ai/reference/bf-back-button-service.md +40 -0
- package/ai/reference/bf-badge-variant.md +29 -0
- package/ai/reference/bf-button-size.md +29 -0
- package/ai/reference/bf-button-type.md +28 -0
- package/ai/reference/bf-button-variant.md +29 -0
- package/ai/reference/bf-card-padding.md +26 -0
- package/ai/reference/bf-card-variant.md +28 -0
- package/ai/reference/bf-chip-group-mode.md +27 -0
- package/ai/reference/bf-chip-variant.md +26 -0
- package/ai/reference/bf-compare-with.md +24 -0
- package/ai/reference/bf-component-overlay-ref.md +33 -0
- package/ai/reference/bf-confirm-button-role.md +26 -0
- package/ai/reference/bf-confirm-button.md +27 -0
- package/ai/reference/bf-confirm-options.md +31 -0
- package/ai/reference/bf-confirm-size.md +26 -0
- package/ai/reference/bf-content-scroll-event.md +21 -0
- package/ai/reference/bf-defaults-options.md +32 -0
- package/ai/reference/bf-defaults.md +28 -0
- package/ai/reference/bf-desktop-toast-config.md +28 -0
- package/ai/reference/bf-desktop-toast-harness.md +33 -0
- package/ai/reference/bf-desktop-toast-options.md +28 -0
- package/ai/reference/bf-desktop-toast-position.md +28 -0
- package/ai/reference/bf-desktop-toast-service.md +42 -0
- package/ai/reference/bf-dialog-config.md +44 -0
- package/ai/reference/bf-dialog-harness.md +52 -0
- package/ai/reference/bf-dialog-ref.md +44 -0
- package/ai/reference/bf-dialog-role.md +26 -0
- package/ai/reference/bf-dialog-service.md +55 -0
- package/ai/reference/bf-dialog-size.md +31 -0
- package/ai/reference/bf-dialog-template-context.md +32 -0
- package/ai/reference/bf-empty-state-heading-level.md +26 -0
- package/ai/reference/bf-enter-key-hint.md +31 -0
- package/ai/reference/bf-field-control-class.md +32 -0
- package/ai/reference/bf-field-control-state.md +28 -0
- package/ai/reference/bf-field-size.md +29 -0
- package/ai/reference/bf-haptics-impact-style.md +27 -0
- package/ai/reference/bf-haptics-notification-type.md +27 -0
- package/ai/reference/bf-haptics-service.md +41 -0
- package/ai/reference/bf-harness-filters.md +29 -0
- package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
- package/ai/reference/bf-icon-button-variant.md +28 -0
- package/ai/reference/bf-icon-size.md +27 -0
- package/ai/reference/bf-input-autocapitalize.md +36 -0
- package/ai/reference/bf-input-enter-key-hint.md +38 -0
- package/ai/reference/bf-input-input-mode.md +38 -0
- package/ai/reference/bf-input-mode.md +32 -0
- package/ai/reference/bf-input-type.md +35 -0
- package/ai/reference/bf-jsdom-shim-options.md +22 -0
- package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
- package/ai/reference/bf-keyboard-assist-directive.md +185 -0
- package/ai/reference/bf-keyboard-phase.md +30 -0
- package/ai/reference/bf-keyboard-service.md +50 -0
- package/ai/reference/bf-labels-overrides.md +26 -0
- package/ai/reference/bf-labels.md +44 -0
- package/ai/reference/bf-menu-button-harness.md +40 -0
- package/ai/reference/bf-menu-button-variant.md +27 -0
- package/ai/reference/bf-menu-divider-directive.md +13 -0
- package/ai/reference/bf-menu-group-directive.md +14 -0
- package/ai/reference/bf-menu-harness.md +55 -0
- package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
- package/ai/reference/bf-menu-item-directive.md +31 -0
- package/ai/reference/bf-menu-item-harness.md +52 -0
- package/ai/reference/bf-menu-item-radio-directive.md +29 -0
- package/ai/reference/bf-menu-placement.md +29 -0
- package/ai/reference/bf-menu-trigger-directive.md +46 -0
- package/ai/reference/bf-modal-options.md +30 -0
- package/ai/reference/bf-modal-presentation.md +26 -0
- package/ai/reference/bf-modal-service.md +38 -0
- package/ai/reference/bf-nav-controller.md +59 -0
- package/ai/reference/bf-nav-direction.md +23 -0
- package/ai/reference/bf-nav-target.md +21 -0
- package/ai/reference/bf-number-input-mode.md +28 -0
- package/ai/reference/bf-number-input-step.md +25 -0
- package/ai/reference/bf-overlay-auto-focus.md +29 -0
- package/ai/reference/bf-overlay-controller.md +22 -0
- package/ai/reference/bf-overlay-host.md +36 -0
- package/ai/reference/bf-overlay-options.md +53 -0
- package/ai/reference/bf-overlay-position.md +30 -0
- package/ai/reference/bf-overlay-ref.md +53 -0
- package/ai/reference/bf-overlay-role.md +27 -0
- package/ai/reference/bf-overlay-service.md +53 -0
- package/ai/reference/bf-overlay-stack-options.md +24 -0
- package/ai/reference/bf-overlay-stack.md +81 -0
- package/ai/reference/bf-overlay-template-context.md +24 -0
- package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
- package/ai/reference/bf-page-context.md +24 -0
- package/ai/reference/bf-platform-service.md +53 -0
- package/ai/reference/bf-platform.md +28 -0
- package/ai/reference/bf-progress-size.md +26 -0
- package/ai/reference/bf-progress-tone.md +28 -0
- package/ai/reference/bf-radio-group-harness.md +49 -0
- package/ai/reference/bf-radio-group-orientation.md +26 -0
- package/ai/reference/bf-radio-option.md +30 -0
- package/ai/reference/bf-refresher-refresh-event.md +22 -0
- package/ai/reference/bf-segment-option.md +29 -0
- package/ai/reference/bf-segmented-harness.md +48 -0
- package/ai/reference/bf-segmented-size.md +29 -0
- package/ai/reference/bf-select-harness.md +70 -0
- package/ai/reference/bf-select-option.md +29 -0
- package/ai/reference/bf-sheet-auto-focus.md +29 -0
- package/ai/reference/bf-sheet-options.md +32 -0
- package/ai/reference/bf-sheet-ref.md +45 -0
- package/ai/reference/bf-sheet-service.md +34 -0
- package/ai/reference/bf-shell-config-mode.md +28 -0
- package/ai/reference/bf-shell-config.md +38 -0
- package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
- package/ai/reference/bf-shell-dynamic-type.md +25 -0
- package/ai/reference/bf-shell-mode.md +28 -0
- package/ai/reference/bf-shell-testing-options.md +26 -0
- package/ai/reference/bf-size.md +45 -0
- package/ai/reference/bf-spinner-size.md +27 -0
- package/ai/reference/bf-stack-change.md +22 -0
- package/ai/reference/bf-stack-reuse-strategy.md +32 -0
- package/ai/reference/bf-stack-transition-event.md +26 -0
- package/ai/reference/bf-stack-transition.md +26 -0
- package/ai/reference/bf-stacked-overlay.md +23 -0
- package/ai/reference/bf-system-bars-style.md +38 -0
- package/ai/reference/bf-tab-bar-controller.md +51 -0
- package/ai/reference/bf-tab-bar-host.md +52 -0
- package/ai/reference/bf-tab-context.md +32 -0
- package/ai/reference/bf-tab-links.md +37 -0
- package/ai/reference/bf-testing-options.md +27 -0
- package/ai/reference/bf-text-scale-service.md +35 -0
- package/ai/reference/bf-theme.md +27 -0
- package/ai/reference/bf-toast-action.md +25 -0
- package/ai/reference/bf-toast-controller.md +17 -0
- package/ai/reference/bf-toast-dismiss-reason.md +29 -0
- package/ai/reference/bf-toast-options.md +29 -0
- package/ai/reference/bf-toast-position.md +26 -0
- package/ai/reference/bf-toast-ref.md +41 -0
- package/ai/reference/bf-toast-service.md +39 -0
- package/ai/reference/bf-toggle-label-position.md +26 -0
- package/ai/reference/bf-transition-kind.md +28 -0
- package/ai/reference/bf-validation-message.md +31 -0
- package/ai/reference/bf-validation-messages.md +33 -0
- package/ai/reference/bf-view-event.md +28 -0
- package/ai/reference/bf-view-lifecycle.md +41 -0
- package/ai/reference/bf-view-state.md +28 -0
- package/ai/reference/bf_default_labels.md +21 -0
- package/ai/reference/bf_default_values.md +21 -0
- package/ai/reference/bf_defaults.md +21 -0
- package/ai/reference/bf_desktop_toast_defaults.md +21 -0
- package/ai/reference/bf_dialog_data.md +17 -0
- package/ai/reference/bf_labels.md +21 -0
- package/ai/reference/bf_overlay_data.md +19 -0
- package/ai/reference/bf_overlay_host.md +21 -0
- package/ai/reference/bf_sheet_default_snap_points.md +17 -0
- package/ai/reference/bf_shell_config.md +21 -0
- package/ai/reference/bf_shell_defaults.md +21 -0
- package/ai/reference/bf_tab_bar.md +21 -0
- package/ai/reference/bf_tab_bar_controller.md +21 -0
- package/ai/reference/bf_tab_context.md +21 -0
- package/ai/reference/bf_tab_links.md +21 -0
- package/ai/reference/bf_theme_storage.md +19 -0
- package/ai/reference/create-transition.md +36 -0
- package/ai/reference/duration_fast.md +17 -0
- package/ai/reference/duration_md.md +17 -0
- package/ai/reference/duration_overlay.md +17 -0
- package/ai/reference/duration_push.md +17 -0
- package/ai/reference/fade-down.md +26 -0
- package/ai/reference/fade-in.md +25 -0
- package/ai/reference/fade-out.md +25 -0
- package/ai/reference/fade-up.md +26 -0
- package/ai/reference/inject-shell-mode.md +23 -0
- package/ai/reference/install-bf-jsdom-shims.md +31 -0
- package/ai/reference/ios_duration.md +17 -0
- package/ai/reference/ios_easing.md +17 -0
- package/ai/reference/is-text-entry.md +25 -0
- package/ai/reference/max_complete_ms.md +17 -0
- package/ai/reference/md-transition.md +31 -0
- package/ai/reference/md_duration.md +17 -0
- package/ai/reference/md_easing.md +17 -0
- package/ai/reference/overlay_duration.md +21 -0
- package/ai/reference/overlay_easing.md +19 -0
- package/ai/reference/overlay_reduced_duration.md +17 -0
- package/ai/reference/pop-frames-at.md +25 -0
- package/ai/reference/prefers-reduced-motion.md +25 -0
- package/ai/reference/provide-bf-defaults.md +33 -0
- package/ai/reference/provide-bf-desktop-toast.md +28 -0
- package/ai/reference/provide-bf-labels.md +38 -0
- package/ai/reference/provide-bf-shell-testing.md +34 -0
- package/ai/reference/provide-bf-shell.md +41 -0
- package/ai/reference/provide-bf-testing.md +33 -0
- package/ai/reference/reduced-transition.md +28 -0
- package/ai/reference/reduced_duration.md +17 -0
- package/ai/reference/resolve-shell-mode.md +33 -0
- package/ai/reference/scale-in.md +25 -0
- package/ai/reference/settle-duration.md +25 -0
- package/ai/reference/settle-pop.md +33 -0
- package/ai/reference/settle_easing.md +17 -0
- package/ai/reference/should-complete-pop.md +26 -0
- package/ai/reference/slide-down.md +26 -0
- package/ai/reference/slide-up.md +26 -0
- package/ai/reference/with-bf-shell-routing.md +19 -0
- package/bin/bf-ui.js +81 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui.mjs +7765 -0
- package/fesm2022/bitforgehq-ui.mjs.map +1 -0
- package/llms-full.txt +25394 -0
- package/llms.txt +329 -0
- package/package.json +94 -0
- package/theme.css +253 -0
- package/types/bitforgehq-ui-icons.d.ts +50 -0
- package/types/bitforgehq-ui-mobile.d.ts +2914 -0
- package/types/bitforgehq-ui-number-input.d.ts +282 -0
- package/types/bitforgehq-ui-overlay.d.ts +1514 -0
- package/types/bitforgehq-ui-testing.d.ts +495 -0
- package/types/bitforgehq-ui.d.ts +3499 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
# bf-modal
|
|
2
|
+
|
|
3
|
+
Modal overlay shown as an inset card over the scaled-down page, or full screen
|
|
4
|
+
|
|
5
|
+
`BfModalComponent` from `@bitforgehq/ui/mobile` · Overlays · since 0.2.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, inject, signal } from '@angular/core';
|
|
11
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
12
|
+
import {
|
|
13
|
+
BfContentComponent,
|
|
14
|
+
BfHeaderComponent,
|
|
15
|
+
type BfModalPresentation,
|
|
16
|
+
BfModalService,
|
|
17
|
+
BfPageComponent,
|
|
18
|
+
} from '@bitforgehq/ui/mobile';
|
|
19
|
+
import { type ShowcaseModalData, ShowcaseModalComponent } from './showcase-modal.component';
|
|
20
|
+
|
|
21
|
+
/** `bf-modal` showcase: the same note editor as a page-sheet card and as a fullscreen modal. */
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'app-modal-showcase',
|
|
24
|
+
imports: [BfPageComponent, BfHeaderComponent, BfContentComponent, BfButtonComponent],
|
|
25
|
+
template: `
|
|
26
|
+
<bf-page data-showcase="modal">
|
|
27
|
+
<bf-header title="Modal" [largeTitle]="false" />
|
|
28
|
+
<bf-content>
|
|
29
|
+
<div class="flex flex-col gap-4 p-4">
|
|
30
|
+
<p class="text-caption text-text-muted">
|
|
31
|
+
In iPhone mode the sheet presentation scales the page behind it down; the fullscreen one
|
|
32
|
+
leaves it as it is.
|
|
33
|
+
</p>
|
|
34
|
+
<bf-button (clicked)="open('sheet')">Open sheet modal</bf-button>
|
|
35
|
+
<bf-button variant="tonal" (clicked)="open('fullscreen')"
|
|
36
|
+
>Open fullscreen modal</bf-button
|
|
37
|
+
>
|
|
38
|
+
<p data-showcase-result class="text-body">{{ result() }}</p>
|
|
39
|
+
</div>
|
|
40
|
+
</bf-content>
|
|
41
|
+
</bf-page>
|
|
42
|
+
`,
|
|
43
|
+
})
|
|
44
|
+
export class ModalShowcasePage {
|
|
45
|
+
private readonly modals = inject(BfModalService);
|
|
46
|
+
|
|
47
|
+
protected readonly result = signal('No note yet.');
|
|
48
|
+
|
|
49
|
+
async open(presentation: BfModalPresentation): Promise<void> {
|
|
50
|
+
const data: ShowcaseModalData = { presentation };
|
|
51
|
+
const ref = this.modals.open<ShowcaseModalComponent, string>(ShowcaseModalComponent, {
|
|
52
|
+
data,
|
|
53
|
+
presentation,
|
|
54
|
+
ariaLabel: 'New note',
|
|
55
|
+
});
|
|
56
|
+
const title = await ref.afterClosed;
|
|
57
|
+
this.result.set(title ? `Saved “${title}”.` : 'Closed without saving.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Overview
|
|
63
|
+
|
|
64
|
+
The modal frame that `BfModalService.open()` renders on the overlay core, `position:
|
|
65
|
+
'fullscreen'`: a `sheet` presentation (default) is a card whose top sits 10 px below the top
|
|
66
|
+
safe area (UIKit's page-sheet inset; `bg-bg rounded-t-card shadow-floating`) over the parked page stack, which scales down and
|
|
67
|
+
grows rounded corners behind it (`--bf-modal-depth`, written by `BfOverlayStack`, consumed by
|
|
68
|
+
`bf-stack-outlet`'s host rule); `fullscreen` covers the viewport edge to edge with no scale.
|
|
69
|
+
Like `bf-page`, it provides a fresh `BfPageContext` so the content's `bf-header` and
|
|
70
|
+
`bf-content` work inside it (with `mainLandmark` false, so its content is no `main` landmark,
|
|
71
|
+
and in the `sheet` presentation with `safeAreaTop` false, so the header adds no status-bar band
|
|
72
|
+
inside the card) — the content supplies its own header (typically a close
|
|
73
|
+
`bf-icon-button` in `start`) and content region. While the soft keyboard overlays the bottom of
|
|
74
|
+
a `sheet` card (iOS, a mobile browser), the card is padded at the bottom by the covered height,
|
|
75
|
+
so the content's `bf-content` ends at the keyboard's top edge and its keyboard assist reveals
|
|
76
|
+
the focused control there. Not used directly in templates.
|
|
77
|
+
|
|
78
|
+
A page-level modal. `BfModalService.open(component, options)` renders a component in a `bf-modal`
|
|
79
|
+
frame on the overlay core, either a page-sheet card over the page stack (default) or a fullscreen
|
|
80
|
+
frame. Unlike `bf-sheet`, there is no backdrop dismiss and no drag-to-dismiss — the content closes
|
|
81
|
+
itself, typically from a close control in its own `bf-header`. You never place `<bf-modal>` in a
|
|
82
|
+
template yourself.
|
|
83
|
+
|
|
84
|
+
## When to use
|
|
85
|
+
|
|
86
|
+
Use `BfModalService` in the Capacitor shell for a self-contained task that takes over the screen and closes itself: compose a message, edit a profile. Use [BfSheetService](https://bitforge-ui.web.app/api/bf-sheet-service) instead for content the user can drag away or keep half open, and [BfDialogService](https://bitforge-ui.web.app/api/bf-dialog-service) on desktop.
|
|
87
|
+
|
|
88
|
+
## Basic usage
|
|
89
|
+
|
|
90
|
+
The content is an ordinary standalone component with its own `bf-header` (`largeTitle="false"`)
|
|
91
|
+
and `bf-content` — `bf-modal` gives it a `BfPageContext`, exactly like `bf-page`, so the two work
|
|
92
|
+
together (the header can, for example, collapse against the content's scroll).
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { Component, inject, signal } from '@angular/core';
|
|
96
|
+
import { BfButtonComponent, BfIconButtonComponent, BfInputComponent } from '@bitforgehq/ui';
|
|
97
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
98
|
+
import { BfContentComponent, BfHeaderComponent, BfModalService } from '@bitforgehq/ui/mobile';
|
|
99
|
+
import { BF_OVERLAY_DATA, BfOverlayRef } from '@bitforgehq/ui/overlay';
|
|
100
|
+
import { X } from 'lucide';
|
|
101
|
+
|
|
102
|
+
/** A task as the modal edits it. */
|
|
103
|
+
export interface Task {
|
|
104
|
+
id: string;
|
|
105
|
+
title: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The modal: its own header with a close button and Save, then the form. */
|
|
109
|
+
@Component({
|
|
110
|
+
selector: 'app-edit-task',
|
|
111
|
+
imports: [
|
|
112
|
+
BfHeaderComponent,
|
|
113
|
+
BfContentComponent,
|
|
114
|
+
BfIconButtonComponent,
|
|
115
|
+
BfIconComponent,
|
|
116
|
+
BfButtonComponent,
|
|
117
|
+
BfInputComponent,
|
|
118
|
+
],
|
|
119
|
+
template: `
|
|
120
|
+
<bf-header title="Edit task" [largeTitle]="false">
|
|
121
|
+
<bf-icon-button bfSlot="start" variant="text" label="Close" (clicked)="close()">
|
|
122
|
+
<bf-icon [icon]="xIcon" />
|
|
123
|
+
</bf-icon-button>
|
|
124
|
+
<bf-button bfSlot="end" variant="text" (clicked)="save()">Save</bf-button>
|
|
125
|
+
</bf-header>
|
|
126
|
+
<bf-content class="px-4">
|
|
127
|
+
<bf-input label="Title" [(value)]="title" />
|
|
128
|
+
</bf-content>
|
|
129
|
+
`,
|
|
130
|
+
})
|
|
131
|
+
export class EditTaskComponent {
|
|
132
|
+
private readonly data = inject<{ taskId: string }>(BF_OVERLAY_DATA);
|
|
133
|
+
private readonly ref = inject<BfOverlayRef<Task>>(BfOverlayRef);
|
|
134
|
+
protected readonly xIcon = X;
|
|
135
|
+
protected readonly title = signal('Buy milk');
|
|
136
|
+
|
|
137
|
+
close(): void {
|
|
138
|
+
this.ref.close();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
save(): void {
|
|
142
|
+
this.ref.close({ id: this.data.taskId, title: this.title() });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A modal that edits a task.
|
|
148
|
+
*
|
|
149
|
+
* `open()` passes the task id as data and awaits the saved task, or `undefined` when closed
|
|
150
|
+
* without a result.
|
|
151
|
+
*
|
|
152
|
+
* @preview none
|
|
153
|
+
*/
|
|
154
|
+
@Component({
|
|
155
|
+
selector: 'app-example-modal-basic',
|
|
156
|
+
imports: [BfButtonComponent],
|
|
157
|
+
template: `
|
|
158
|
+
<div class="flex flex-col items-start gap-3">
|
|
159
|
+
<bf-button (clicked)="edit('task-1')">Edit task</bf-button>
|
|
160
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
161
|
+
</div>
|
|
162
|
+
`,
|
|
163
|
+
})
|
|
164
|
+
export class ModalBasicExampleComponent {
|
|
165
|
+
private readonly modals = inject(BfModalService);
|
|
166
|
+
protected readonly result = signal('');
|
|
167
|
+
|
|
168
|
+
protected async edit(taskId: string): Promise<void> {
|
|
169
|
+
const ref = this.modals.open<EditTaskComponent, Task>(EditTaskComponent, {
|
|
170
|
+
data: { taskId },
|
|
171
|
+
});
|
|
172
|
+
const saved = await ref.afterClosed; // undefined when closed without a result
|
|
173
|
+
this.result.set(saved ? `Saved "${saved.title}".` : 'Closed.');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Presentations
|
|
179
|
+
|
|
180
|
+
`presentation: 'sheet'` (default) is a card whose top edge sits 10 px below the top safe area
|
|
181
|
+
(`top-[calc(var(--spacing-safe-top)+10px)]`, UIKit's page-sheet inset, so the card starts below
|
|
182
|
+
the status bar and the parent page peeks above it; `bg-bg rounded-t-card shadow-floating`) over
|
|
183
|
+
the parked page stack; in iOS mode opening it scales the page stack down and
|
|
184
|
+
rounds its corners behind the card (Material mode leaves the page stack full size) (driven by `--bf-modal-depth`, written by `BfOverlayStack` on every
|
|
185
|
+
registered `bf-stack-outlet` root, consumed by the outlet's own host styles). `presentation:
|
|
186
|
+
'fullscreen'` covers the viewport edge to edge and leaves the page stack full size — use it for
|
|
187
|
+
forms and flows that need the whole screen.
|
|
188
|
+
|
|
189
|
+
The sheet card already sits below the status bar, so it sets its `BfPageContext`'s `safeAreaTop`
|
|
190
|
+
to false: the content's `bf-header` (and a `fullscreen` `bf-content`) adds no status-bar band of
|
|
191
|
+
its own inside the card. A fullscreen modal reaches the top of the screen and keeps `safeAreaTop`
|
|
192
|
+
true, like a page.
|
|
193
|
+
|
|
194
|
+
A `bf-page` inside a sheet modal's content (for example a stack of its own) is not supported: it
|
|
195
|
+
provides a fresh `BfPageContext` with `safeAreaTop` true, so its header would add the status-bar
|
|
196
|
+
band back inside the card. Put the `bf-header` and `bf-content` directly in the modal's content
|
|
197
|
+
component, or use the `fullscreen` presentation for a flow that needs its own pages.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { Component, inject } from '@angular/core';
|
|
201
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
202
|
+
import { BfModalService } from '@bitforgehq/ui/mobile';
|
|
203
|
+
import { BfOverlayRef } from '@bitforgehq/ui/overlay';
|
|
204
|
+
|
|
205
|
+
/** Onboarding that fills the screen. */
|
|
206
|
+
@Component({
|
|
207
|
+
selector: 'app-onboarding',
|
|
208
|
+
imports: [BfButtonComponent],
|
|
209
|
+
template: `
|
|
210
|
+
<div class="flex flex-col gap-3 p-4">
|
|
211
|
+
<p class="text-body">Welcome! Three steps set up your workspace.</p>
|
|
212
|
+
<bf-button (clicked)="ref.close()">Done</bf-button>
|
|
213
|
+
</div>
|
|
214
|
+
`,
|
|
215
|
+
})
|
|
216
|
+
export class OnboardingComponent {
|
|
217
|
+
protected readonly ref = inject(BfOverlayRef);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* A full-screen modal.
|
|
222
|
+
*
|
|
223
|
+
* `presentation: 'fullscreen'` covers the whole screen instead of the card.
|
|
224
|
+
*
|
|
225
|
+
* @preview none
|
|
226
|
+
*/
|
|
227
|
+
@Component({
|
|
228
|
+
selector: 'app-example-modal-presentations',
|
|
229
|
+
imports: [BfButtonComponent],
|
|
230
|
+
template: `<bf-button (clicked)="open()">Start onboarding</bf-button>`,
|
|
231
|
+
})
|
|
232
|
+
export class ModalPresentationsExampleComponent {
|
|
233
|
+
private readonly modals = inject(BfModalService);
|
|
234
|
+
|
|
235
|
+
protected open(): void {
|
|
236
|
+
this.modals.open(OnboardingComponent, { presentation: 'fullscreen' });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
In iOS mode, stacking two sheet modals scales the page stack twice as much (`--bf-modal-depth: 2`); a
|
|
242
|
+
fullscreen modal never adds to that depth.
|
|
243
|
+
|
|
244
|
+
iOS mode slides the card (or frame) up from the bottom and back down on close; Material mode fades
|
|
245
|
+
it up 16 px and back down.
|
|
246
|
+
|
|
247
|
+
## Dismissible
|
|
248
|
+
|
|
249
|
+
By default (`dismissible: true`) `Escape` and the hardware back button close the modal in addition
|
|
250
|
+
to the content's own close control. With `dismissible: false` only the content closes it: `Escape`
|
|
251
|
+
is ignored and the back button is swallowed while the modal is open instead of falling through to
|
|
252
|
+
the page behind (like a non-dismissible `bf-sheet`). There is no backdrop dismiss for either case —
|
|
253
|
+
give the content an explicit close control.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { Component, inject } from '@angular/core';
|
|
257
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
258
|
+
import { BfModalService } from '@bitforgehq/ui/mobile';
|
|
259
|
+
import { BfOverlayRef } from '@bitforgehq/ui/overlay';
|
|
260
|
+
|
|
261
|
+
/** Terms that must be accepted. */
|
|
262
|
+
@Component({
|
|
263
|
+
selector: 'app-terms',
|
|
264
|
+
imports: [BfButtonComponent],
|
|
265
|
+
template: `
|
|
266
|
+
<div class="flex flex-col gap-3 p-4">
|
|
267
|
+
<p class="text-body">Accept the updated terms to continue.</p>
|
|
268
|
+
<bf-button (clicked)="ref.close()">Done</bf-button>
|
|
269
|
+
</div>
|
|
270
|
+
`,
|
|
271
|
+
})
|
|
272
|
+
export class TermsComponent {
|
|
273
|
+
protected readonly ref = inject(BfOverlayRef);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* A modal that must be answered.
|
|
278
|
+
*
|
|
279
|
+
* `dismissible: false` turns off the swipe down and the back button; only its own button closes it.
|
|
280
|
+
*
|
|
281
|
+
* @preview none
|
|
282
|
+
*/
|
|
283
|
+
@Component({
|
|
284
|
+
selector: 'app-example-modal-dismissible',
|
|
285
|
+
imports: [BfButtonComponent],
|
|
286
|
+
template: `<bf-button (clicked)="open()">Show the terms</bf-button>`,
|
|
287
|
+
})
|
|
288
|
+
export class ModalDismissibleExampleComponent {
|
|
289
|
+
private readonly modals = inject(BfModalService);
|
|
290
|
+
|
|
291
|
+
protected open(): void {
|
|
292
|
+
this.modals.open(TermsComponent, { dismissible: false });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Navigation
|
|
298
|
+
|
|
299
|
+
A navigation to another URL path closes the modal, resolving `afterClosed` with `undefined`, also
|
|
300
|
+
a `dismissible: false` one. A query- or fragment-only change leaves it open; a matrix-parameter
|
|
301
|
+
change (`/list;sort=name`) is a path change and closes it. In the `/mobile` shell every push,
|
|
302
|
+
every pop (the header back button, swipe-back, `BfNavController.back()`) and every tab switch
|
|
303
|
+
changes the path, so a modal opened on a page closes when that page is left. The Android hardware
|
|
304
|
+
back button with a modal open closes the modal only (a `dismissible: false` modal swallows the
|
|
305
|
+
press); its handler runs at priority 100, above the page pop, so one press never closes it and
|
|
306
|
+
pops the page as well. Pass `closeOnNavigation: false` for a modal that should stay over the next
|
|
307
|
+
page.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { Component, inject } from '@angular/core';
|
|
311
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
312
|
+
import { BfModalService } from '@bitforgehq/ui/mobile';
|
|
313
|
+
import { BfOverlayRef } from '@bitforgehq/ui/overlay';
|
|
314
|
+
|
|
315
|
+
/** A message being written. */
|
|
316
|
+
@Component({
|
|
317
|
+
selector: 'app-compose',
|
|
318
|
+
imports: [BfButtonComponent],
|
|
319
|
+
template: `
|
|
320
|
+
<div class="flex flex-col gap-3 p-4">
|
|
321
|
+
<p class="text-body">Your draft stays while you look something up.</p>
|
|
322
|
+
<bf-button (clicked)="ref.close()">Done</bf-button>
|
|
323
|
+
</div>
|
|
324
|
+
`,
|
|
325
|
+
})
|
|
326
|
+
export class ComposeComponent {
|
|
327
|
+
protected readonly ref = inject(BfOverlayRef);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* A modal that stays open across navigations.
|
|
332
|
+
*
|
|
333
|
+
* `closeOnNavigation: false` keeps a compose modal open while the page behind it changes.
|
|
334
|
+
*
|
|
335
|
+
* @preview none
|
|
336
|
+
*/
|
|
337
|
+
@Component({
|
|
338
|
+
selector: 'app-example-modal-navigation',
|
|
339
|
+
imports: [BfButtonComponent],
|
|
340
|
+
template: `<bf-button (clicked)="open()">Compose</bf-button>`,
|
|
341
|
+
})
|
|
342
|
+
export class ModalNavigationExampleComponent {
|
|
343
|
+
private readonly modals = inject(BfModalService);
|
|
344
|
+
|
|
345
|
+
protected open(): void {
|
|
346
|
+
this.modals.open(ComposeComponent, { closeOnNavigation: false });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
A modal opened before the first navigation ends (an onboarding modal from the root component or
|
|
352
|
+
an `APP_INITIALIZER`) closes when the start URL redirects (`''` to `'today'`): open it after the
|
|
353
|
+
first `NavigationEnd`, or pass `closeOnNavigation: false`. A `history.pushState()` made outside
|
|
354
|
+
Angular's `Location` is not seen.
|
|
355
|
+
|
|
356
|
+
A route `canDeactivate` guard protects a form modal's unsaved input only against in-app
|
|
357
|
+
navigations: the modal stays open while the guard's confirm asks. On the web the browser's back
|
|
358
|
+
button changes the URL before the guard runs, so the modal has already closed when "Stay" is
|
|
359
|
+
answered (the Android hardware back button never gets that far: the modal takes it first). A
|
|
360
|
+
modal that must keep its draft passes `closeOnNavigation: false` or saves the draft as it
|
|
361
|
+
changes.
|
|
362
|
+
|
|
363
|
+
## Soft keyboard
|
|
364
|
+
|
|
365
|
+
A sheet modal ends above the soft keyboard. Where the keyboard overlays the page (iOS with
|
|
366
|
+
`Keyboard.resize: 'none'`, a mobile browser), `bf-modal` pads its bottom by the covered height
|
|
367
|
+
while the keyboard is up, so the content's `bf-content` ends at the keyboard's top edge and its
|
|
368
|
+
keyboard assist scrolls the focused field 24 px clear of it. Hiding the keyboard removes the
|
|
369
|
+
padding. On Android the WebView itself shrinks, so nothing is added; a fullscreen modal leaves the
|
|
370
|
+
keyboard to its `bf-content`, as a page does. Put the form in the content's `bf-content` and its
|
|
371
|
+
primary action in the header (`end` slot), where the keyboard never reaches it.
|
|
372
|
+
|
|
373
|
+
## Labels and the injector
|
|
374
|
+
|
|
375
|
+
Default strings come from `provideBfLabels()` (`modal`, "Modal"). The service is a root singleton, so it reads
|
|
376
|
+
the app-level labels; to use a feature's own `provideBfLabels()` (in a component's `providers`),
|
|
377
|
+
pass that component's injector. It then also becomes the parent of the modal's content injector,
|
|
378
|
+
so the content resolves the feature's providers.
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
import { Component, Injector, inject } from '@angular/core';
|
|
382
|
+
import { BfButtonComponent, provideBfLabels } from '@bitforgehq/ui';
|
|
383
|
+
import { BfModalService } from '@bitforgehq/ui/mobile';
|
|
384
|
+
import { BfOverlayRef } from '@bitforgehq/ui/overlay';
|
|
385
|
+
|
|
386
|
+
/** A note in the feature's language. */
|
|
387
|
+
@Component({
|
|
388
|
+
selector: 'app-note',
|
|
389
|
+
imports: [BfButtonComponent],
|
|
390
|
+
template: `
|
|
391
|
+
<div class="flex flex-col gap-3 p-4">
|
|
392
|
+
<p class="text-body">Notiz bearbeiten</p>
|
|
393
|
+
<bf-button (clicked)="ref.close()">Fertig</bf-button>
|
|
394
|
+
</div>
|
|
395
|
+
`,
|
|
396
|
+
})
|
|
397
|
+
export class NoteComponent {
|
|
398
|
+
protected readonly ref = inject(BfOverlayRef);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* A modal that reads a feature's labels.
|
|
403
|
+
*
|
|
404
|
+
* This feature provides German labels; passing the opener's injector is what names the modal
|
|
405
|
+
* "Fenster" for screen readers (its `modal` label) and lets its content read the same labels.
|
|
406
|
+
*
|
|
407
|
+
* @preview none
|
|
408
|
+
*/
|
|
409
|
+
@Component({
|
|
410
|
+
selector: 'app-example-modal-injector',
|
|
411
|
+
imports: [BfButtonComponent],
|
|
412
|
+
providers: [provideBfLabels({ modal: 'Fenster' })],
|
|
413
|
+
template: `<bf-button (clicked)="open()">Notiz bearbeiten</bf-button>`,
|
|
414
|
+
})
|
|
415
|
+
export class ModalInjectorExampleComponent {
|
|
416
|
+
private readonly modals = inject(BfModalService);
|
|
417
|
+
private readonly injector = inject(Injector);
|
|
418
|
+
|
|
419
|
+
protected open(): void {
|
|
420
|
+
this.modals.open(NoteComponent, { injector: this.injector });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Desktop
|
|
426
|
+
|
|
427
|
+
`BfModalService`'s modal is phone-shaped and meant for the Capacitor shell (`provideBfShell()`). On
|
|
428
|
+
a desktop web app, use the desktop overlays from `@bitforgehq/ui/overlay` instead: they need only
|
|
429
|
+
`@angular/cdk`, no Capacitor and no router. A desktop dialog locks the page's scroll, makes the page
|
|
430
|
+
behind it `inert`, and traps and restores focus.
|
|
431
|
+
|
|
432
|
+
`BfDialogService.open()` renders the same kind of content (your component, with a typed ref and
|
|
433
|
+
`BF_DIALOG_DATA`) centred over a backdrop; lay it out with `bf-dialog`.
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
import { Component, inject, signal } from '@angular/core';
|
|
437
|
+
import { BfButtonComponent, BfInputComponent } from '@bitforgehq/ui';
|
|
438
|
+
import {
|
|
439
|
+
BF_DIALOG_DATA,
|
|
440
|
+
BfDialogComponent,
|
|
441
|
+
BfDialogRef,
|
|
442
|
+
BfDialogService,
|
|
443
|
+
} from '@bitforgehq/ui/overlay';
|
|
444
|
+
|
|
445
|
+
/** The dialog: a `bf-dialog` that closes itself with the new name. */
|
|
446
|
+
@Component({
|
|
447
|
+
selector: 'app-rename-dialog',
|
|
448
|
+
imports: [BfDialogComponent, BfButtonComponent, BfInputComponent],
|
|
449
|
+
template: `
|
|
450
|
+
<bf-dialog title="Rename project">
|
|
451
|
+
<bf-input label="Name" [(value)]="name" />
|
|
452
|
+
<div bfSlot="footer">
|
|
453
|
+
<bf-button variant="outline" (clicked)="ref.close()">Cancel</bf-button>
|
|
454
|
+
<bf-button (clicked)="ref.close(name())">Save</bf-button>
|
|
455
|
+
</div>
|
|
456
|
+
</bf-dialog>
|
|
457
|
+
`,
|
|
458
|
+
})
|
|
459
|
+
export class RenameDialogComponent {
|
|
460
|
+
protected readonly ref = inject<BfDialogRef<string>>(BfDialogRef);
|
|
461
|
+
protected readonly name = signal(inject<{ name: string }>(BF_DIALOG_DATA).name);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* A dialog that renames a project.
|
|
466
|
+
*
|
|
467
|
+
* `open()` passes the current name as data and awaits the result: the new name, or `undefined`
|
|
468
|
+
* when the dialog is dismissed.
|
|
469
|
+
*/
|
|
470
|
+
@Component({
|
|
471
|
+
selector: 'app-example-dialog-basic',
|
|
472
|
+
imports: [BfButtonComponent],
|
|
473
|
+
template: `
|
|
474
|
+
<div class="flex flex-col items-start gap-3">
|
|
475
|
+
<p class="text-heading">{{ project().name }}</p>
|
|
476
|
+
<bf-button (clicked)="rename()">Rename…</bf-button>
|
|
477
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
478
|
+
</div>
|
|
479
|
+
`,
|
|
480
|
+
})
|
|
481
|
+
export class DialogBasicExampleComponent {
|
|
482
|
+
private readonly dialogs = inject(BfDialogService);
|
|
483
|
+
protected readonly project = signal({ name: 'Atlas rebrand' });
|
|
484
|
+
protected readonly result = signal('');
|
|
485
|
+
|
|
486
|
+
protected async rename(): Promise<void> {
|
|
487
|
+
const ref = this.dialogs.open<RenameDialogComponent, { name: string }, string>(
|
|
488
|
+
RenameDialogComponent,
|
|
489
|
+
{ data: { name: this.project().name } },
|
|
490
|
+
);
|
|
491
|
+
const newName = await ref.afterClosed; // undefined when dismissed
|
|
492
|
+
if (newName) {
|
|
493
|
+
this.project.set({ name: newName });
|
|
494
|
+
}
|
|
495
|
+
this.result.set(newName ? `Renamed to ${newName}.` : 'Not renamed.');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Theming
|
|
501
|
+
|
|
502
|
+
- Sheet card: `bg-bg`, `rounded-t-card` (`--radius-card`), `shadow-floating`; its top at
|
|
503
|
+
`--spacing-safe-top` plus 10 px.
|
|
504
|
+
- Fullscreen frame: `bg-bg`, no radius or shadow.
|
|
505
|
+
- Page stack behind a sheet modal (iOS mode): scaled and rounded through `--bf-modal-depth` on the
|
|
506
|
+
`bf-stack-outlet` host — do not set that variable yourself.
|
|
507
|
+
- Keyboard: an inline `padding-bottom` on the sheet card while the keyboard overlays it — do not
|
|
508
|
+
set your own inline bottom padding on `bf-modal`.
|
|
509
|
+
|
|
510
|
+
## Accessibility
|
|
511
|
+
|
|
512
|
+
The overlay panel is a `role="dialog"` with `aria-modal="true"` and a name: the
|
|
513
|
+
`ariaLabel` option when given; else the first heading in the content that is not `aria-hidden`
|
|
514
|
+
(`h1`–`h6` or `role="heading"`, such as the `bf-header` title), found once the content has
|
|
515
|
+
rendered (or when it first appears later, e.g. behind `@if`) and referenced through `aria-labelledby` (it gets an id `bf-overlay-title-<n>` when it has
|
|
516
|
+
none); else `provideBfLabels()`'s `modal` ("Modal"). The page stack behind it is `inert`. Focus moves to the first focusable element of the
|
|
517
|
+
content (or the panel), unlike a sheet, which focuses its panel; Tab is trapped inside, and focus
|
|
518
|
+
returns to the element that opened the modal on close. The content's `bf-content` is no `main`
|
|
519
|
+
landmark (the modal's `BfPageContext.mainLandmark` is false): content in a dialog is not the
|
|
520
|
+
page's main content, and the page behind keeps its `main`, `inert` while the modal is open.
|
|
521
|
+
`Escape`, the hardware back button and, in a browser with `CloseWatcher`, Android back close a
|
|
522
|
+
dismissible modal. Under
|
|
523
|
+
`prefers-reduced-motion` the modal cross-fades in and out instead of sliding or rising, and the
|
|
524
|
+
page stack behind a sheet modal skips the scale/rounding transition.
|
|
525
|
+
|
|
526
|
+
### Keyboard
|
|
527
|
+
|
|
528
|
+
| Key | Action |
|
|
529
|
+
| ------------------------------------------------ | ------------------------------------------------------ |
|
|
530
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between the modal's controls, wrapping inside it |
|
|
531
|
+
| <kbd>Escape</kbd> | Closes a dismissible modal |
|
|
532
|
+
|
|
533
|
+
## Notes
|
|
534
|
+
|
|
535
|
+
### Do
|
|
536
|
+
|
|
537
|
+
- Give the content its own `bf-header` with a close control (`bf-icon-button` in `start`) —
|
|
538
|
+
`bf-modal` never adds one for you.
|
|
539
|
+
- Use `presentation: 'sheet'` for editors and detail forms opened from a page; reserve
|
|
540
|
+
`'fullscreen'` for onboarding and other flows that need the whole screen.
|
|
541
|
+
- Close with a result through the injected `BfOverlayRef` and await `afterClosed` in the caller.
|
|
542
|
+
|
|
543
|
+
### Don't
|
|
544
|
+
|
|
545
|
+
- Don't rely on a backdrop tap or a drag to close the modal — there is neither; always give the
|
|
546
|
+
content a close control.
|
|
547
|
+
- Don't use `dismissible: false` without a way out in the content itself (a close or cancel
|
|
548
|
+
control) — the user would otherwise be stuck.
|
|
549
|
+
- Don't nest a `bf-modal` inside another modal's content by hand; open the second one through
|
|
550
|
+
`BfModalService` so it stacks and the depth scale is correct.
|
|
551
|
+
- Don't wrap a sheet modal's content in a `bf-page`: it brings the status-bar band back (see
|
|
552
|
+
Presentations).
|
|
553
|
+
|
|
554
|
+
## Migration notes
|
|
555
|
+
|
|
556
|
+
### 0.3.0
|
|
557
|
+
|
|
558
|
+
**Behaviour:** a navigation to another URL path closes it with no result, also a
|
|
559
|
+
`dismissible: false` one; new `closeOnNavigation` option (default true). Migration: pass
|
|
560
|
+
`closeOnNavigation: false` to keep it open (see Migration 19).
|
|
561
|
+
|
|
562
|
+
**Behaviour:** named by the first heading: without `ariaLabel`, the panel is named by the first
|
|
563
|
+
heading in its content that is not `aria-hidden` (such as the `bf-header` title) through
|
|
564
|
+
`aria-labelledby`, once the content has rendered; without a heading, by `provideBfLabels()`'s
|
|
565
|
+
`modal` ("Modal"). An explicit `ariaLabel` always wins.
|
|
566
|
+
|
|
567
|
+
New `injector` option: labels resolve from it per call (default the root injector) and it becomes
|
|
568
|
+
the parent of the content's injector, so a feature-level `provideBfLabels()` and the feature's
|
|
569
|
+
other providers reach the modal.
|
|
570
|
+
|
|
571
|
+
A heading that renders after the overlay opened (behind `@if`, a `resource()`) still names it: the
|
|
572
|
+
panel is watched until its first heading appears.
|
|
573
|
+
|
|
574
|
+
JSDoc of `modal.component.ts` for the API reference: `@summary`/`@since` on the exported symbols. No
|
|
575
|
+
behaviour change.
|
|
576
|
+
|
|
577
|
+
JSDoc of `modal.service.ts` for the API reference: `@summary`/`@since` on the exported symbols;
|
|
578
|
+
`@default` on the option properties the code applies; `@param` docs. No behaviour change.
|
|
579
|
+
|
|
580
|
+
**Behaviour:** the page-sheet card (`presentation: 'sheet'`) starts 10 px below the top safe
|
|
581
|
+
area (`top-[calc(var(--spacing-safe-top)+10px)]`, UIKit's page-sheet inset; was `top-3`), so it
|
|
582
|
+
sits below the status bar and the parent page peeks above it, and it sets its `BfPageContext`'s
|
|
583
|
+
`safeAreaTop` to false, so its content's header adds no second status-bar band (Migration 29). A
|
|
584
|
+
fullscreen modal is unchanged.
|
|
585
|
+
|
|
586
|
+
**Behaviour:** its content is no `main` landmark: `bf-modal` sets its `BfPageContext.mainLandmark`
|
|
587
|
+
to false (content in a dialog is not the page's main content; the page behind stays `main`, inert
|
|
588
|
+
while the modal is open). Android web back closes it where `CloseWatcher` exists (Migration 35).
|
|
589
|
+
|
|
590
|
+
### 0.2.0
|
|
591
|
+
|
|
592
|
+
New `BfModalService.open(component, { data, presentation, dismissible, ariaLabel })` in
|
|
593
|
+
`@bitforgehq/ui/mobile`, a page-level modal on the overlay core (`position: 'fullscreen'`, back
|
|
594
|
+
priority 100, no backdrop dismiss). `presentation: 'sheet'` (default) renders `bf-modal` as a card
|
|
595
|
+
inset 12 px from the top (`bg-bg rounded-t-card shadow-floating`) and, in iOS mode only, counts as
|
|
596
|
+
a modal, driving `--bf-modal-depth` on every registered `bf-stack-outlet` root (see
|
|
597
|
+
`### stack-outlet`) so the page behind scales down (Material mode leaves it unscaled);
|
|
598
|
+
`'fullscreen'` renders it edge to edge (`bg-bg`, no radius or shadow) and does not count towards
|
|
599
|
+
that depth. iOS mode slides the frame up on enter and down on exit; Material mode fades it up 16 px
|
|
600
|
+
on enter and down on exit (new `fadeUp`/`fadeDown` helpers in `overlay-animations.ts`). It returns
|
|
601
|
+
the `BfOverlayRef<T>` the content also injects (with the caller's `data` as `BF_OVERLAY_DATA`) and
|
|
602
|
+
closes with a result through; unlike `bf-sheet` there is no drag-to-dismiss, so the content needs
|
|
603
|
+
its own close control. `dismissible: false` (default true) ignores `Escape` and swallows the
|
|
604
|
+
hardware back button while open instead of closing or falling through to the page behind. Like
|
|
605
|
+
`bf-page`, `bf-modal` provides a fresh `BfPageContext` so the content's own `bf-header`
|
|
606
|
+
(`largeTitle="false"`, typically a close `bf-icon-button` in `start`) and `bf-content` work
|
|
607
|
+
inside it. `provideBfShell()` now also provides `BfSheetService` and `BfModalService`.
|
|
608
|
+
|
|
609
|
+
Fixed (behaviour change): a `presentation: 'sheet'` modal ends above the soft keyboard. While the
|
|
610
|
+
keyboard overlays the card's bottom edge (iOS, a mobile browser), `bf-modal` gets an inline
|
|
611
|
+
`padding-bottom` of the covered height, so the content's `bf-content` ends at the keyboard's top
|
|
612
|
+
edge and its keyboard assist reveals the focused control there; it goes away with the keyboard. A
|
|
613
|
+
`fullscreen` modal, and Android, where the WebView resizes, are unchanged.
|