@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,962 @@
|
|
|
1
|
+
# bf-dialog
|
|
2
|
+
|
|
3
|
+
Desktop modal dialog with a title, scrolling body, footer actions and focus trap
|
|
4
|
+
|
|
5
|
+
`BfDialogComponent` from `@bitforgehq/ui/overlay` · Overlays · since 0.3.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, inject, signal } from '@angular/core';
|
|
11
|
+
import { BfButtonComponent, BfInputComponent } from '@bitforgehq/ui';
|
|
12
|
+
import {
|
|
13
|
+
BF_DIALOG_DATA,
|
|
14
|
+
BfDialogComponent,
|
|
15
|
+
BfDialogRef,
|
|
16
|
+
BfDialogService,
|
|
17
|
+
BfMenuButtonComponent,
|
|
18
|
+
BfMenuComponent,
|
|
19
|
+
BfMenuItemDirective,
|
|
20
|
+
} from '@bitforgehq/ui/overlay';
|
|
21
|
+
|
|
22
|
+
/** The project a demo dialog edits. */
|
|
23
|
+
export interface DemoProject {
|
|
24
|
+
name: string;
|
|
25
|
+
owner: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The form dialog of the demo: two fields, Cancel and Save. */
|
|
29
|
+
@Component({
|
|
30
|
+
selector: 'app-dialog-demo-edit',
|
|
31
|
+
imports: [BfDialogComponent, BfButtonComponent, BfInputComponent],
|
|
32
|
+
template: `
|
|
33
|
+
<bf-dialog title="Edit project">
|
|
34
|
+
<bf-input label="Name" [(value)]="name" />
|
|
35
|
+
<bf-input label="Owner" [(value)]="owner" />
|
|
36
|
+
<div bfSlot="footer">
|
|
37
|
+
<bf-button variant="outline" (clicked)="ref.close()">Cancel</bf-button>
|
|
38
|
+
<bf-button (clicked)="save()">Save</bf-button>
|
|
39
|
+
</div>
|
|
40
|
+
</bf-dialog>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class DialogDemoEditComponent {
|
|
44
|
+
protected readonly ref = inject<BfDialogRef<DemoProject>>(BfDialogRef);
|
|
45
|
+
private readonly project = inject<DemoProject>(BF_DIALOG_DATA);
|
|
46
|
+
|
|
47
|
+
protected readonly name = signal(this.project.name);
|
|
48
|
+
protected readonly owner = signal(this.project.owner);
|
|
49
|
+
|
|
50
|
+
protected save(): void {
|
|
51
|
+
this.ref.close({ name: this.name(), owner: this.owner() });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The sections of the long dialog: more text than fits, so its body scrolls. */
|
|
56
|
+
const RELEASE_NOTES: readonly { title: string; body: string }[] = [
|
|
57
|
+
{ title: 'Boards', body: 'Columns keep their width when you collapse the sidebar.' },
|
|
58
|
+
{ title: 'Search', body: 'Results group by project, and the newest match comes first.' },
|
|
59
|
+
{ title: 'Invoices', body: 'Drafts save every few seconds, so a closed tab loses nothing.' },
|
|
60
|
+
{ title: 'Calendar', body: 'Week view starts on the day your team settings name.' },
|
|
61
|
+
{ title: 'Files', body: 'Uploads resume after a lost connection instead of starting over.' },
|
|
62
|
+
{ title: 'Comments', body: 'Mentions notify once, even when you edit the comment later.' },
|
|
63
|
+
{ title: 'Reports', body: 'Exports keep the filters and the column order you chose.' },
|
|
64
|
+
{ title: 'Tasks', body: 'Recurring tasks skip public holidays when you ask them to.' },
|
|
65
|
+
{ title: 'Team', body: 'Guests see only the projects they were invited to.' },
|
|
66
|
+
{ title: 'Settings', body: 'The theme follows your system until you pick one yourself.' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/** A dialog whose body is taller than the viewport allows: the body scrolls, the page does not. */
|
|
70
|
+
@Component({
|
|
71
|
+
selector: 'app-dialog-demo-notes',
|
|
72
|
+
imports: [BfDialogComponent, BfButtonComponent],
|
|
73
|
+
template: `
|
|
74
|
+
<bf-dialog title="Release notes">
|
|
75
|
+
@for (note of notes; track note.title) {
|
|
76
|
+
<section class="flex flex-col gap-1">
|
|
77
|
+
<h3 class="text-label font-bold">{{ note.title }}</h3>
|
|
78
|
+
<p class="text-body text-text-secondary">{{ note.body }}</p>
|
|
79
|
+
<p class="text-body text-text-secondary">
|
|
80
|
+
Nothing changes for your existing data, and you can switch it back in the settings.
|
|
81
|
+
</p>
|
|
82
|
+
</section>
|
|
83
|
+
}
|
|
84
|
+
<div bfSlot="footer">
|
|
85
|
+
<bf-button (clicked)="ref.close(true)">Got it</bf-button>
|
|
86
|
+
</div>
|
|
87
|
+
</bf-dialog>
|
|
88
|
+
`,
|
|
89
|
+
})
|
|
90
|
+
export class DialogDemoNotesComponent {
|
|
91
|
+
protected readonly ref = inject<BfDialogRef<boolean>>(BfDialogRef);
|
|
92
|
+
protected readonly notes = RELEASE_NOTES;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Who can open a shared project link. */
|
|
96
|
+
export type DemoAccess = 'Only the team' | 'Anyone with the link';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A dialog with a menu button in its footer: `Escape` in the open menu closes the menu only, and
|
|
100
|
+
* a second `Escape` closes the dialog.
|
|
101
|
+
*/
|
|
102
|
+
@Component({
|
|
103
|
+
selector: 'app-dialog-demo-share',
|
|
104
|
+
imports: [
|
|
105
|
+
BfDialogComponent,
|
|
106
|
+
BfButtonComponent,
|
|
107
|
+
BfMenuButtonComponent,
|
|
108
|
+
BfMenuComponent,
|
|
109
|
+
BfMenuItemDirective,
|
|
110
|
+
],
|
|
111
|
+
template: `
|
|
112
|
+
<bf-dialog title="Share project">
|
|
113
|
+
<p class="text-body text-text-secondary">Link access: {{ access() }}</p>
|
|
114
|
+
<div bfSlot="footer">
|
|
115
|
+
<bf-menu-button data-testid="dialog-share-access" [menu]="accessMenu">
|
|
116
|
+
Link access
|
|
117
|
+
</bf-menu-button>
|
|
118
|
+
<bf-button (clicked)="ref.close(access())">Done</bf-button>
|
|
119
|
+
</div>
|
|
120
|
+
</bf-dialog>
|
|
121
|
+
|
|
122
|
+
<ng-template #accessMenu>
|
|
123
|
+
<bf-menu>
|
|
124
|
+
<button bfMenuItem (triggered)="access.set('Only the team')">Only the team</button>
|
|
125
|
+
<button bfMenuItem (triggered)="access.set('Anyone with the link')">
|
|
126
|
+
Anyone with the link
|
|
127
|
+
</button>
|
|
128
|
+
</bf-menu>
|
|
129
|
+
</ng-template>
|
|
130
|
+
`,
|
|
131
|
+
})
|
|
132
|
+
export class DialogDemoShareComponent {
|
|
133
|
+
protected readonly ref = inject<BfDialogRef<DemoAccess>>(BfDialogRef);
|
|
134
|
+
protected readonly access = signal<DemoAccess>('Only the team');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The bf-dialog overview.
|
|
139
|
+
*
|
|
140
|
+
* Live demo of `bf-dialog` and `BfDialogService`, rendered in place: "Edit project" opens a form
|
|
141
|
+
* dialog, "Delete…" a `confirm()` alert dialog, "Release notes" a dialog whose body scrolls and
|
|
142
|
+
* "Share…" a dialog with a menu button in its footer; the result line announces each outcome
|
|
143
|
+
* (the `confirm()` answer id included). The triggers and the result line carry `data-testid`s
|
|
144
|
+
* for the end-to-end suite.
|
|
145
|
+
*/
|
|
146
|
+
@Component({
|
|
147
|
+
selector: 'app-example-dialog-overview',
|
|
148
|
+
imports: [BfButtonComponent],
|
|
149
|
+
template: `
|
|
150
|
+
<div class="flex flex-col gap-4">
|
|
151
|
+
<p class="text-heading">{{ project().name }}</p>
|
|
152
|
+
<p class="text-body text-text-secondary">Owner: {{ project().owner }}</p>
|
|
153
|
+
<div class="flex flex-wrap gap-2">
|
|
154
|
+
<bf-button data-testid="dialog-edit" (clicked)="edit()">Edit project</bf-button>
|
|
155
|
+
<bf-button data-testid="dialog-delete" variant="danger" (clicked)="remove()">
|
|
156
|
+
Delete…
|
|
157
|
+
</bf-button>
|
|
158
|
+
<bf-button data-testid="dialog-notes" variant="outline" (clicked)="readNotes()">
|
|
159
|
+
Release notes
|
|
160
|
+
</bf-button>
|
|
161
|
+
<bf-button data-testid="dialog-share" variant="tonal" (clicked)="share()">
|
|
162
|
+
Share…
|
|
163
|
+
</bf-button>
|
|
164
|
+
</div>
|
|
165
|
+
<p
|
|
166
|
+
data-status
|
|
167
|
+
data-testid="dialog-result"
|
|
168
|
+
class="text-caption text-text-muted"
|
|
169
|
+
aria-live="polite"
|
|
170
|
+
>
|
|
171
|
+
{{ result() }}
|
|
172
|
+
</p>
|
|
173
|
+
</div>
|
|
174
|
+
`,
|
|
175
|
+
})
|
|
176
|
+
export class DialogOverviewExampleComponent {
|
|
177
|
+
private readonly dialogs = inject(BfDialogService);
|
|
178
|
+
|
|
179
|
+
readonly project = signal<DemoProject>({ name: 'Atlas rebrand', owner: 'Mia Keller' });
|
|
180
|
+
readonly result = signal('');
|
|
181
|
+
|
|
182
|
+
async edit(): Promise<void> {
|
|
183
|
+
const ref = this.dialogs.open<DialogDemoEditComponent, DemoProject, DemoProject>(
|
|
184
|
+
DialogDemoEditComponent,
|
|
185
|
+
{ data: this.project() },
|
|
186
|
+
);
|
|
187
|
+
const saved = await ref.afterClosed;
|
|
188
|
+
if (saved) {
|
|
189
|
+
this.project.set(saved);
|
|
190
|
+
this.result.set('Saved.');
|
|
191
|
+
} else {
|
|
192
|
+
this.result.set('Edit cancelled.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async remove(): Promise<void> {
|
|
197
|
+
const answer = await this.dialogs.confirm({
|
|
198
|
+
title: `Delete ${this.project().name}?`,
|
|
199
|
+
message: 'Its tasks and files are deleted too. This cannot be undone.',
|
|
200
|
+
buttons: [
|
|
201
|
+
{ id: 'cancel', label: 'Cancel', role: 'cancel' },
|
|
202
|
+
{ id: 'delete', label: 'Delete', role: 'destructive' },
|
|
203
|
+
],
|
|
204
|
+
});
|
|
205
|
+
this.result.set(
|
|
206
|
+
answer === 'delete'
|
|
207
|
+
? 'Answer "delete": deleted (not really, this is a demo).'
|
|
208
|
+
: `Answer "${answer ?? 'none'}": kept.`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async readNotes(): Promise<void> {
|
|
213
|
+
const read = await this.dialogs.open<DialogDemoNotesComponent, unknown, boolean>(
|
|
214
|
+
DialogDemoNotesComponent,
|
|
215
|
+
).afterClosed;
|
|
216
|
+
this.result.set(read ? 'Release notes read.' : 'Release notes closed.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async share(): Promise<void> {
|
|
220
|
+
const access = await this.dialogs.open<DialogDemoShareComponent, unknown, DemoAccess>(
|
|
221
|
+
DialogDemoShareComponent,
|
|
222
|
+
).afterClosed;
|
|
223
|
+
this.result.set(access ? `Link access: ${access}.` : 'Sharing unchanged.');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Overview
|
|
229
|
+
|
|
230
|
+
Layout of a modal dialog opened with `BfDialogService`: a title row, a scrolling body and a
|
|
231
|
+
footer of actions. The panel is labelled by the title, and the body becomes focusable only
|
|
232
|
+
while its content overflows, so keyboard users can scroll it (a region named by the title when
|
|
233
|
+
there is one).
|
|
234
|
+
|
|
235
|
+
A modal dialog for desktop and web apps, from `@bitforgehq/ui/overlay` (needs `@angular/cdk`, no
|
|
236
|
+
Capacitor). `BfDialogService.open()` renders your component or template centred over a backdrop
|
|
237
|
+
and returns a typed `BfDialogRef`; `bf-dialog` inside the content lays out the title, the
|
|
238
|
+
scrolling body and the footer actions. `confirm()` opens an alert dialog in one call.
|
|
239
|
+
|
|
240
|
+
## When to use
|
|
241
|
+
|
|
242
|
+
Use `BfDialogService` for a task or a decision that needs the user's full attention before the page goes on: a form, a detail view, a destructive confirmation (`confirm()`). Use [bf-menu](https://bitforge-ui.web.app/components/menu) instead for a short list of actions, [BfDesktopToastService](https://bitforge-ui.web.app/api/bf-desktop-toast-service) for a status that needs no answer, and [bf-alert](https://bitforge-ui.web.app/components/alert) for information that fits in the page. In the Capacitor shell, use [BfModalService](https://bitforge-ui.web.app/api/bf-modal-service) or [BfAlertDialogService](https://bitforge-ui.web.app/api/bf-alert-dialog-service) instead.
|
|
243
|
+
|
|
244
|
+
## Basic usage
|
|
245
|
+
|
|
246
|
+
Write the dialog as a component whose template is a `bf-dialog`. It injects `BfDialogRef` to
|
|
247
|
+
close itself and `BF_DIALOG_DATA` for its input. Put the actions in `bfSlot="footer"`.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { Component, inject, signal } from '@angular/core';
|
|
251
|
+
import { BfButtonComponent, BfInputComponent } from '@bitforgehq/ui';
|
|
252
|
+
import {
|
|
253
|
+
BF_DIALOG_DATA,
|
|
254
|
+
BfDialogComponent,
|
|
255
|
+
BfDialogRef,
|
|
256
|
+
BfDialogService,
|
|
257
|
+
} from '@bitforgehq/ui/overlay';
|
|
258
|
+
|
|
259
|
+
/** The dialog: a `bf-dialog` that closes itself with the new name. */
|
|
260
|
+
@Component({
|
|
261
|
+
selector: 'app-rename-dialog',
|
|
262
|
+
imports: [BfDialogComponent, BfButtonComponent, BfInputComponent],
|
|
263
|
+
template: `
|
|
264
|
+
<bf-dialog title="Rename project">
|
|
265
|
+
<bf-input label="Name" [(value)]="name" />
|
|
266
|
+
<div bfSlot="footer">
|
|
267
|
+
<bf-button variant="outline" (clicked)="ref.close()">Cancel</bf-button>
|
|
268
|
+
<bf-button (clicked)="ref.close(name())">Save</bf-button>
|
|
269
|
+
</div>
|
|
270
|
+
</bf-dialog>
|
|
271
|
+
`,
|
|
272
|
+
})
|
|
273
|
+
export class RenameDialogComponent {
|
|
274
|
+
protected readonly ref = inject<BfDialogRef<string>>(BfDialogRef);
|
|
275
|
+
protected readonly name = signal(inject<{ name: string }>(BF_DIALOG_DATA).name);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* A dialog that renames a project.
|
|
280
|
+
*
|
|
281
|
+
* `open()` passes the current name as data and awaits the result: the new name, or `undefined`
|
|
282
|
+
* when the dialog is dismissed.
|
|
283
|
+
*/
|
|
284
|
+
@Component({
|
|
285
|
+
selector: 'app-example-dialog-basic',
|
|
286
|
+
imports: [BfButtonComponent],
|
|
287
|
+
template: `
|
|
288
|
+
<div class="flex flex-col items-start gap-3">
|
|
289
|
+
<p class="text-heading">{{ project().name }}</p>
|
|
290
|
+
<bf-button (clicked)="rename()">Rename…</bf-button>
|
|
291
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
292
|
+
</div>
|
|
293
|
+
`,
|
|
294
|
+
})
|
|
295
|
+
export class DialogBasicExampleComponent {
|
|
296
|
+
private readonly dialogs = inject(BfDialogService);
|
|
297
|
+
protected readonly project = signal({ name: 'Atlas rebrand' });
|
|
298
|
+
protected readonly result = signal('');
|
|
299
|
+
|
|
300
|
+
protected async rename(): Promise<void> {
|
|
301
|
+
const ref = this.dialogs.open<RenameDialogComponent, { name: string }, string>(
|
|
302
|
+
RenameDialogComponent,
|
|
303
|
+
{ data: { name: this.project().name } },
|
|
304
|
+
);
|
|
305
|
+
const newName = await ref.afterClosed; // undefined when dismissed
|
|
306
|
+
if (newName) {
|
|
307
|
+
this.project.set({ name: newName });
|
|
308
|
+
}
|
|
309
|
+
this.result.set(newName ? `Renamed to ${newName}.` : 'Not renamed.');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Sizes
|
|
315
|
+
|
|
316
|
+
`size` sets the dialog's width on its own scale (not `BfSize`, which is a control-height
|
|
317
|
+
scale): `sm` 24 rem, `md` 32 rem (default), `lg` 48 rem, `xl` 64 rem. Each is capped at the
|
|
318
|
+
viewport width minus the gutter on both sides, and the height at the viewport height minus the
|
|
319
|
+
gutters; the body scrolls when the content is taller. `fullscreen` fills the viewport without a
|
|
320
|
+
card radius.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { Component, inject } from '@angular/core';
|
|
324
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
325
|
+
import { BfDialogComponent, BfDialogRef, BfDialogService } from '@bitforgehq/ui/overlay';
|
|
326
|
+
|
|
327
|
+
/** A wide report. */
|
|
328
|
+
@Component({
|
|
329
|
+
selector: 'app-report-dialog',
|
|
330
|
+
imports: [BfDialogComponent, BfButtonComponent],
|
|
331
|
+
template: `
|
|
332
|
+
<bf-dialog title="Quarterly report">
|
|
333
|
+
<p>Revenue grew in every region; the details follow in the full report.</p>
|
|
334
|
+
<div bfSlot="footer"><bf-button (clicked)="ref.close()">Close</bf-button></div>
|
|
335
|
+
</bf-dialog>
|
|
336
|
+
`,
|
|
337
|
+
})
|
|
338
|
+
export class ReportDialogComponent {
|
|
339
|
+
protected readonly ref = inject(BfDialogRef);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** A full-screen editor. */
|
|
343
|
+
@Component({
|
|
344
|
+
selector: 'app-editor-dialog',
|
|
345
|
+
imports: [BfDialogComponent, BfButtonComponent],
|
|
346
|
+
template: `
|
|
347
|
+
<bf-dialog title="Edit the brief">
|
|
348
|
+
<p>A full-screen dialog fills the viewport, without a card radius.</p>
|
|
349
|
+
<div bfSlot="footer"><bf-button (clicked)="ref.close()">Done</bf-button></div>
|
|
350
|
+
</bf-dialog>
|
|
351
|
+
`,
|
|
352
|
+
})
|
|
353
|
+
export class EditorDialogComponent {
|
|
354
|
+
protected readonly ref = inject(BfDialogRef);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** A large dialog and a full-screen dialog. */
|
|
358
|
+
@Component({
|
|
359
|
+
selector: 'app-example-dialog-sizes',
|
|
360
|
+
imports: [BfButtonComponent],
|
|
361
|
+
template: `
|
|
362
|
+
<div class="flex flex-wrap gap-2">
|
|
363
|
+
<bf-button variant="outline" (clicked)="dialogs.open(report, { size: 'lg' })"
|
|
364
|
+
>Large</bf-button
|
|
365
|
+
>
|
|
366
|
+
<bf-button variant="outline" (clicked)="dialogs.open(editor, { size: 'fullscreen' })"
|
|
367
|
+
>Full screen</bf-button
|
|
368
|
+
>
|
|
369
|
+
</div>
|
|
370
|
+
`,
|
|
371
|
+
})
|
|
372
|
+
export class DialogSizesExampleComponent {
|
|
373
|
+
protected readonly dialogs = inject(BfDialogService);
|
|
374
|
+
protected readonly report = ReportDialogComponent;
|
|
375
|
+
protected readonly editor = EditorDialogComponent;
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Header, body and footer
|
|
380
|
+
|
|
381
|
+
`title` renders as the `h2` the dialog is labelled by. Anything with `bfSlot="header"` sits
|
|
382
|
+
after the title in the header row (a status chip, a step count). The default slot is the body;
|
|
383
|
+
`bfSlot="footer"` is an end-aligned row of actions that wraps on narrow screens. The close button
|
|
384
|
+
(×) at the top end closes with no result; hide it with `[closeButton]="false"` when the footer
|
|
385
|
+
has a cancel action.
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { Component, inject, signal } from '@angular/core';
|
|
389
|
+
import { BfBadgeComponent, BfButtonComponent } from '@bitforgehq/ui';
|
|
390
|
+
import { BfDialogComponent, BfDialogRef, BfDialogService } from '@bitforgehq/ui/overlay';
|
|
391
|
+
|
|
392
|
+
/** A dialog with a header badge, a body and footer actions, and no close button. */
|
|
393
|
+
@Component({
|
|
394
|
+
selector: 'app-invite-dialog',
|
|
395
|
+
imports: [BfBadgeComponent, BfButtonComponent, BfDialogComponent],
|
|
396
|
+
template: `
|
|
397
|
+
<bf-dialog title="Invite people" [closeButton]="false">
|
|
398
|
+
<bf-badge bfSlot="header">Step 2 of 3</bf-badge>
|
|
399
|
+
<p>Add the people who work on this project.</p>
|
|
400
|
+
<div bfSlot="footer">
|
|
401
|
+
<bf-button variant="outline" (clicked)="ref.close()">Back</bf-button>
|
|
402
|
+
<bf-button (clicked)="ref.close('next')">Next</bf-button>
|
|
403
|
+
</div>
|
|
404
|
+
</bf-dialog>
|
|
405
|
+
`,
|
|
406
|
+
})
|
|
407
|
+
export class InviteDialogComponent {
|
|
408
|
+
protected readonly ref = inject<BfDialogRef<string>>(BfDialogRef);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* The header, body and footer of a dialog.
|
|
413
|
+
*
|
|
414
|
+
* The title and a step badge in the header, the body text, then Back and Next in the footer.
|
|
415
|
+
*/
|
|
416
|
+
@Component({
|
|
417
|
+
selector: 'app-example-dialog-sections',
|
|
418
|
+
imports: [BfButtonComponent],
|
|
419
|
+
template: `
|
|
420
|
+
<div class="flex flex-col items-start gap-3">
|
|
421
|
+
<bf-button (clicked)="invite()">Invite people</bf-button>
|
|
422
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
423
|
+
</div>
|
|
424
|
+
`,
|
|
425
|
+
})
|
|
426
|
+
export class DialogSectionsExampleComponent {
|
|
427
|
+
private readonly dialogs = inject(BfDialogService);
|
|
428
|
+
protected readonly result = signal('');
|
|
429
|
+
|
|
430
|
+
protected async invite(): Promise<void> {
|
|
431
|
+
const step = await this.dialogs.open<InviteDialogComponent, unknown, string>(
|
|
432
|
+
InviteDialogComponent,
|
|
433
|
+
).afterClosed;
|
|
434
|
+
this.result.set(step === 'next' ? 'On to step 3.' : 'Back to step 1.');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Template dialogs
|
|
440
|
+
|
|
441
|
+
A template opens with the same options. Its context is `{ $implicit: ref, data }`.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import { Component, type TemplateRef, inject, viewChild } from '@angular/core';
|
|
445
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
446
|
+
import {
|
|
447
|
+
BfDialogComponent,
|
|
448
|
+
BfDialogService,
|
|
449
|
+
type BfDialogTemplateContext,
|
|
450
|
+
} from '@bitforgehq/ui/overlay';
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* A dialog from a template.
|
|
454
|
+
*
|
|
455
|
+
* The `ng-template` receives `{ $implicit: ref, data }`; `open()` takes the same options as for a
|
|
456
|
+
* component.
|
|
457
|
+
*/
|
|
458
|
+
@Component({
|
|
459
|
+
selector: 'app-example-dialog-template',
|
|
460
|
+
imports: [BfButtonComponent, BfDialogComponent],
|
|
461
|
+
template: `
|
|
462
|
+
<bf-button variant="outline" (clicked)="open()">Keyboard shortcuts</bf-button>
|
|
463
|
+
|
|
464
|
+
<ng-template #shortcuts let-ref let-data="data">
|
|
465
|
+
<bf-dialog title="Keyboard shortcuts">
|
|
466
|
+
<p>{{ data.hint }}</p>
|
|
467
|
+
<div bfSlot="footer"><bf-button (clicked)="ref.close()">Got it</bf-button></div>
|
|
468
|
+
</bf-dialog>
|
|
469
|
+
</ng-template>
|
|
470
|
+
`,
|
|
471
|
+
})
|
|
472
|
+
export class DialogTemplateExampleComponent {
|
|
473
|
+
private readonly dialogs = inject(BfDialogService);
|
|
474
|
+
readonly shortcuts =
|
|
475
|
+
viewChild.required<TemplateRef<BfDialogTemplateContext<{ hint: string }>>>('shortcuts');
|
|
476
|
+
|
|
477
|
+
open(): void {
|
|
478
|
+
this.dialogs.open(this.shortcuts(), { size: 'sm', data: { hint: 'Press ? anywhere.' } });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Confirmations
|
|
484
|
+
|
|
485
|
+
`confirm()` opens an alert dialog with a title, an optional message and a row of buttons, and
|
|
486
|
+
resolves with the pressed button's `id` (its `label` when it has no id). Buttons render in order,
|
|
487
|
+
right-aligned: a `cancel`-role button is `outline`, a `destructive` one `danger`, the first plain
|
|
488
|
+
one `primary`, further plain ones `tonal`. Without `buttons` it shows Cancel and OK from
|
|
489
|
+
`provideBfLabels()` (ids `cancel` and `ok`). `size` is `sm` (default) or `md`.
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
import { Component, inject, signal } from '@angular/core';
|
|
493
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
494
|
+
import { BfDialogService } from '@bitforgehq/ui/overlay';
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* A destructive confirmation.
|
|
498
|
+
*
|
|
499
|
+
* `confirm()` resolves with the pressed button's id: `delete`, `cancel`, or the cancel answer on
|
|
500
|
+
* `Escape`.
|
|
501
|
+
*/
|
|
502
|
+
@Component({
|
|
503
|
+
selector: 'app-example-dialog-confirm',
|
|
504
|
+
imports: [BfButtonComponent],
|
|
505
|
+
template: `
|
|
506
|
+
<div class="flex flex-col items-start gap-3">
|
|
507
|
+
<bf-button variant="danger" (clicked)="remove()">Delete project…</bf-button>
|
|
508
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
509
|
+
</div>
|
|
510
|
+
`,
|
|
511
|
+
})
|
|
512
|
+
export class DialogConfirmExampleComponent {
|
|
513
|
+
private readonly dialogs = inject(BfDialogService);
|
|
514
|
+
protected readonly result = signal('');
|
|
515
|
+
|
|
516
|
+
protected async remove(): Promise<void> {
|
|
517
|
+
const answer = await this.dialogs.confirm({
|
|
518
|
+
title: 'Delete project?',
|
|
519
|
+
message: 'Its tasks and files are deleted too. This cannot be undone.',
|
|
520
|
+
buttons: [
|
|
521
|
+
{ id: 'cancel', label: 'Cancel', role: 'cancel' },
|
|
522
|
+
{ id: 'delete', label: 'Delete', role: 'destructive' },
|
|
523
|
+
],
|
|
524
|
+
});
|
|
525
|
+
this.result.set(answer === 'delete' ? 'Deleted (not really).' : 'Kept.');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Results
|
|
531
|
+
|
|
532
|
+
`BfDialogRef<R, C>` carries the result type `R` and the content type `C`. `close(result)`
|
|
533
|
+
resolves `afterClosed` with it; `Escape`, a backdrop click, the close button and a navigation
|
|
534
|
+
resolve `undefined`. `closed()` turns true once the dialog is gone, and `componentInstance` is the
|
|
535
|
+
content component (`null` for a template).
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
import { Component, inject, signal } from '@angular/core';
|
|
539
|
+
import { BfButtonComponent, BfCheckboxComponent } from '@bitforgehq/ui';
|
|
540
|
+
import {
|
|
541
|
+
BF_DIALOG_DATA,
|
|
542
|
+
BfDialogComponent,
|
|
543
|
+
BfDialogRef,
|
|
544
|
+
BfDialogService,
|
|
545
|
+
} from '@bitforgehq/ui/overlay';
|
|
546
|
+
|
|
547
|
+
/** The filters a list shows. */
|
|
548
|
+
export interface Filters {
|
|
549
|
+
open: boolean;
|
|
550
|
+
mine: boolean;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** A filter dialog that returns the edited filters and can highlight one of them. */
|
|
554
|
+
@Component({
|
|
555
|
+
selector: 'app-filter-dialog',
|
|
556
|
+
imports: [BfButtonComponent, BfCheckboxComponent, BfDialogComponent],
|
|
557
|
+
template: `
|
|
558
|
+
<bf-dialog title="Filters">
|
|
559
|
+
<div class="flex flex-col gap-2">
|
|
560
|
+
<bf-checkbox
|
|
561
|
+
label="Open tasks only"
|
|
562
|
+
[class]="highlighted() === 'status' ? 'bg-primary-soft rounded-field' : ''"
|
|
563
|
+
[(checked)]="open"
|
|
564
|
+
/>
|
|
565
|
+
<bf-checkbox label="Assigned to me" [(checked)]="mine" />
|
|
566
|
+
</div>
|
|
567
|
+
<div bfSlot="footer">
|
|
568
|
+
<bf-button variant="outline" (clicked)="ref.close()">Cancel</bf-button>
|
|
569
|
+
<bf-button (clicked)="ref.close({ open: open(), mine: mine() })">Apply</bf-button>
|
|
570
|
+
</div>
|
|
571
|
+
</bf-dialog>
|
|
572
|
+
`,
|
|
573
|
+
})
|
|
574
|
+
export class FilterDialogComponent {
|
|
575
|
+
protected readonly ref = inject<BfDialogRef<Filters>>(BfDialogRef);
|
|
576
|
+
private readonly current = inject<Filters>(BF_DIALOG_DATA);
|
|
577
|
+
protected readonly open = signal(this.current.open);
|
|
578
|
+
protected readonly mine = signal(this.current.mine);
|
|
579
|
+
protected readonly highlighted = signal('');
|
|
580
|
+
|
|
581
|
+
highlight(field: string): void {
|
|
582
|
+
this.highlighted.set(field);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* A typed dialog result.
|
|
588
|
+
*
|
|
589
|
+
* `BfDialogRef<Filters, FilterDialogComponent>` types both the result and `componentInstance`;
|
|
590
|
+
* a dismissed dialog keeps the current filters.
|
|
591
|
+
*/
|
|
592
|
+
@Component({
|
|
593
|
+
selector: 'app-example-dialog-results',
|
|
594
|
+
imports: [BfButtonComponent],
|
|
595
|
+
template: `
|
|
596
|
+
<div class="flex flex-col items-start gap-3">
|
|
597
|
+
<bf-button variant="outline" (clicked)="edit()">Filters…</bf-button>
|
|
598
|
+
<p class="text-caption text-text-muted">
|
|
599
|
+
{{ filters().open ? 'Open' : 'All' }} tasks · {{ filters().mine ? 'mine' : 'everyone' }}
|
|
600
|
+
</p>
|
|
601
|
+
</div>
|
|
602
|
+
`,
|
|
603
|
+
})
|
|
604
|
+
export class DialogResultsExampleComponent {
|
|
605
|
+
private readonly dialogs = inject(BfDialogService);
|
|
606
|
+
protected readonly filters = signal<Filters>({ open: true, mine: false });
|
|
607
|
+
|
|
608
|
+
protected async edit(): Promise<void> {
|
|
609
|
+
const current = this.filters();
|
|
610
|
+
const ref = this.dialogs.open<FilterDialogComponent, Filters, Filters>(FilterDialogComponent, {
|
|
611
|
+
data: current,
|
|
612
|
+
});
|
|
613
|
+
ref.componentInstance?.highlight('status');
|
|
614
|
+
this.filters.set((await ref.afterClosed) ?? current);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## Focus and dismissal
|
|
620
|
+
|
|
621
|
+
Focus moves to the element marked `autofocus` (or `cdkFocusInitial`) — itself when it is
|
|
622
|
+
focusable (a control, or an element with a `tabindex`, also `-1` for a region to start reading
|
|
623
|
+
at), else the first control inside it — else the first field or button in the body, never the ×,
|
|
624
|
+
which comes last in the tab order. `autoFocus: 'panel'` focuses the dialog itself, so a screen
|
|
625
|
+
reader starts at the top of a long text. `Escape` closes the dialog unless
|
|
626
|
+
`dismissOnEscape: false`; a backdrop click closes a `dialog` unless `dismissOnBackdrop: false`,
|
|
627
|
+
and never an `alertdialog`. In a browser with `CloseWatcher` (Chrome on Android, a PWA), Android
|
|
628
|
+
back closes the dialog the same way as `Escape` and leaves the URL alone; with
|
|
629
|
+
`dismissOnEscape: false` the dialog swallows it and stays open. A desktop browser's Back button is
|
|
630
|
+
no close request: it navigates, and the navigation closes the dialog. On close, focus returns to the element that opened it
|
|
631
|
+
(`restoreFocus: false` to skip); when that element is gone, to the topmost dialog still open,
|
|
632
|
+
else it stays on the page body. After a destructive `confirm()` that removes the opener (a
|
|
633
|
+
deleted row), move focus yourself to a logical place, such as the next row or the list's
|
|
634
|
+
heading. An `alertdialog` you open yourself (`open(…, { role: 'alertdialog' })`) needs
|
|
635
|
+
`ariaDescribedBy` pointing at its message; `confirm()` wires it for you. The service is a root
|
|
636
|
+
singleton: pass
|
|
637
|
+
`injector: inject(Injector)` so a feature-level `provideBfLabels()` names the close button
|
|
638
|
+
(`close`) and the fallback (`dialog`).
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
import {
|
|
642
|
+
Component,
|
|
643
|
+
ElementRef,
|
|
644
|
+
Injector,
|
|
645
|
+
afterNextRender,
|
|
646
|
+
inject,
|
|
647
|
+
signal,
|
|
648
|
+
viewChildren,
|
|
649
|
+
} from '@angular/core';
|
|
650
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
651
|
+
import { BfDialogComponent, BfDialogRef, BfDialogService } from '@bitforgehq/ui/overlay';
|
|
652
|
+
|
|
653
|
+
/** Focuses the native button inside a `bf-button` host (a type guard, not an assertion). */
|
|
654
|
+
function focusButtonIn(host: unknown): void {
|
|
655
|
+
if (host instanceof HTMLElement) {
|
|
656
|
+
host.querySelector('button')?.focus();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/** Terms that must be answered: an alert dialog described by its text. */
|
|
661
|
+
@Component({
|
|
662
|
+
selector: 'app-terms-dialog',
|
|
663
|
+
imports: [BfButtonComponent, BfDialogComponent],
|
|
664
|
+
template: `
|
|
665
|
+
<bf-dialog title="Updated terms" [closeButton]="false">
|
|
666
|
+
<p id="terms-text">We changed how long we keep deleted projects: 30 days instead of 7.</p>
|
|
667
|
+
<div bfSlot="footer"><bf-button (clicked)="ref.close(true)">Accept</bf-button></div>
|
|
668
|
+
</bf-dialog>
|
|
669
|
+
`,
|
|
670
|
+
})
|
|
671
|
+
export class TermsDialogComponent {
|
|
672
|
+
protected readonly ref = inject<BfDialogRef<boolean>>(BfDialogRef);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Initial focus, dismissal and focus after a deletion.
|
|
677
|
+
*
|
|
678
|
+
* The terms open as an `alertdialog` that starts reading at the panel and ignores `Escape`. Each
|
|
679
|
+
* row's Delete confirms first; the deleted row opened the dialog, so focus moves to the next row.
|
|
680
|
+
*/
|
|
681
|
+
@Component({
|
|
682
|
+
selector: 'app-example-dialog-focus',
|
|
683
|
+
imports: [BfButtonComponent],
|
|
684
|
+
template: `
|
|
685
|
+
<div class="flex flex-col items-start gap-3">
|
|
686
|
+
<bf-button variant="outline" (clicked)="showTerms()">Show the terms</bf-button>
|
|
687
|
+
<ul class="flex flex-col gap-2 self-stretch">
|
|
688
|
+
@for (row of rows(); track row) {
|
|
689
|
+
<li class="flex items-center justify-between gap-3">
|
|
690
|
+
<span class="text-body">{{ row }}</span>
|
|
691
|
+
<bf-button #rowButton variant="text" size="sm" (clicked)="remove(row)"
|
|
692
|
+
>Delete {{ row }}</bf-button
|
|
693
|
+
>
|
|
694
|
+
</li>
|
|
695
|
+
}
|
|
696
|
+
</ul>
|
|
697
|
+
</div>
|
|
698
|
+
`,
|
|
699
|
+
})
|
|
700
|
+
export class DialogFocusExampleComponent {
|
|
701
|
+
private readonly dialogs = inject(BfDialogService);
|
|
702
|
+
private readonly injector = inject(Injector);
|
|
703
|
+
private readonly rowButtons = viewChildren('rowButton', {
|
|
704
|
+
read: ElementRef,
|
|
705
|
+
});
|
|
706
|
+
protected readonly rows = signal(['Brief', 'Moodboard', 'Deck']);
|
|
707
|
+
|
|
708
|
+
protected showTerms(): void {
|
|
709
|
+
this.dialogs.open(TermsDialogComponent, {
|
|
710
|
+
role: 'alertdialog',
|
|
711
|
+
ariaDescribedBy: 'terms-text',
|
|
712
|
+
autoFocus: 'panel',
|
|
713
|
+
dismissOnEscape: false,
|
|
714
|
+
injector: this.injector,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
protected async remove(row: string): Promise<void> {
|
|
719
|
+
const answer = await this.dialogs.confirm({
|
|
720
|
+
title: `Delete ${row}?`,
|
|
721
|
+
buttons: [
|
|
722
|
+
{ id: 'cancel', label: 'Cancel', role: 'cancel' },
|
|
723
|
+
{ id: 'delete', label: 'Delete', role: 'destructive' },
|
|
724
|
+
],
|
|
725
|
+
});
|
|
726
|
+
if (answer === 'delete') {
|
|
727
|
+
// After a destructive confirmation, focus the next row: the deleted one opened the dialog.
|
|
728
|
+
const next = this.rows().indexOf(row);
|
|
729
|
+
this.rows.update((rows) => rows.filter((r) => r !== row));
|
|
730
|
+
afterNextRender(
|
|
731
|
+
() => {
|
|
732
|
+
const buttons = this.rowButtons();
|
|
733
|
+
const target = buttons[next] ?? buttons.at(-1);
|
|
734
|
+
focusButtonIn(target?.nativeElement);
|
|
735
|
+
},
|
|
736
|
+
{ injector: this.injector },
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
## Navigation
|
|
744
|
+
|
|
745
|
+
A navigation to another URL path closes the dialog: a router navigation (a link inside it, a
|
|
746
|
+
redirect after a save, a deep link) and the browser's back and forward buttons. `afterClosed`
|
|
747
|
+
resolves `undefined`; `confirm()` resolves the cancel button's answer, else `undefined`. A query-
|
|
748
|
+
or fragment-only change (a tab kept in `?tab=`) leaves it open. It listens through `Location` from
|
|
749
|
+
`@angular/common`, so it works with or without `@angular/router`. A `confirm()` opened from a
|
|
750
|
+
`canDeactivate` guard stays open: the path it records is the one the guard runs on. Focus
|
|
751
|
+
returns to the opener when it is still in the page, as on any close. Pass
|
|
752
|
+
`closeOnNavigation: false` for a dialog that outlives its page, such as one that walks through
|
|
753
|
+
several routes.
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
import { Component, inject } from '@angular/core';
|
|
757
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
758
|
+
import { BfDialogComponent, BfDialogRef, BfDialogService } from '@bitforgehq/ui/overlay';
|
|
759
|
+
|
|
760
|
+
/** A setup wizard that walks through several routes, so it outlives a navigation. */
|
|
761
|
+
@Component({
|
|
762
|
+
selector: 'app-setup-wizard',
|
|
763
|
+
imports: [BfButtonComponent, BfDialogComponent],
|
|
764
|
+
template: `
|
|
765
|
+
<bf-dialog title="Set up your workspace">
|
|
766
|
+
<p>This dialog stays open while the page behind it changes.</p>
|
|
767
|
+
<div bfSlot="footer"><bf-button (clicked)="ref.close()">Finish</bf-button></div>
|
|
768
|
+
</bf-dialog>
|
|
769
|
+
`,
|
|
770
|
+
})
|
|
771
|
+
export class SetupWizardComponent {
|
|
772
|
+
protected readonly ref = inject(BfDialogRef);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** A dialog that stays open across navigations. */
|
|
776
|
+
@Component({
|
|
777
|
+
selector: 'app-example-dialog-navigation',
|
|
778
|
+
imports: [BfButtonComponent],
|
|
779
|
+
template: `<bf-button variant="outline" (clicked)="open()">Start the setup</bf-button>`,
|
|
780
|
+
})
|
|
781
|
+
export class DialogNavigationExampleComponent {
|
|
782
|
+
private readonly dialogs = inject(BfDialogService);
|
|
783
|
+
|
|
784
|
+
protected open(): void {
|
|
785
|
+
this.dialogs.open(SetupWizardComponent, { closeOnNavigation: false });
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
A matrix-parameter change (`/projects;view=grid`) is a path change and closes it. A
|
|
791
|
+
`history.pushState()` made outside Angular's `Location` is not seen and does not. A dialog opened
|
|
792
|
+
before the first navigation ends (from the root component or an `APP_INITIALIZER`) closes when
|
|
793
|
+
the start URL redirects (`''` to `'projects'`): open it after the first `NavigationEnd`, or pass
|
|
794
|
+
`closeOnNavigation: false`.
|
|
795
|
+
|
|
796
|
+
A route `canDeactivate` guard protects a dialog's unsaved input only against in-app navigations:
|
|
797
|
+
the dialog stays open while the guard's `confirm()` asks. The browser's back and forward buttons
|
|
798
|
+
change the URL before the guard runs, so the dialog has already closed when "Stay" is answered. A
|
|
799
|
+
dialog that must keep its draft passes `closeOnNavigation: false` or saves the draft as it
|
|
800
|
+
changes.
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
import { Component, inject, signal } from '@angular/core';
|
|
804
|
+
import { type CanDeactivateFn } from '@angular/router';
|
|
805
|
+
import { BfTextareaComponent } from '@bitforgehq/ui';
|
|
806
|
+
import { BfDialogService } from '@bitforgehq/ui/overlay';
|
|
807
|
+
|
|
808
|
+
/** Asks before the editor's unsaved changes are lost; `true` lets the navigation go on. */
|
|
809
|
+
export const unsavedChanges: CanDeactivateFn<DialogGuardExampleComponent> = async (page) =>
|
|
810
|
+
!page.dirty() ||
|
|
811
|
+
(await inject(BfDialogService).confirm({
|
|
812
|
+
title: 'Discard changes?',
|
|
813
|
+
buttons: [
|
|
814
|
+
{ id: 'stay', label: 'Stay', role: 'cancel' },
|
|
815
|
+
{ id: 'discard', label: 'Discard', role: 'destructive' },
|
|
816
|
+
],
|
|
817
|
+
})) === 'discard';
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* A route guard that asks before unsaved changes are lost.
|
|
821
|
+
*
|
|
822
|
+
* `unsavedChanges` goes on the editor's route:
|
|
823
|
+
* `{ path: 'editor', component: DialogGuardExampleComponent, canDeactivate: [unsavedChanges] }`.
|
|
824
|
+
* The `confirm()` it opens stays open while the guard waits for the answer.
|
|
825
|
+
*
|
|
826
|
+
* @preview none
|
|
827
|
+
*/
|
|
828
|
+
@Component({
|
|
829
|
+
selector: 'app-example-dialog-guard',
|
|
830
|
+
imports: [BfTextareaComponent],
|
|
831
|
+
template: `<bf-textarea label="Draft" [value]="draft()" (valueChange)="edit($event)" />`,
|
|
832
|
+
})
|
|
833
|
+
export class DialogGuardExampleComponent {
|
|
834
|
+
readonly draft = signal('');
|
|
835
|
+
readonly dirty = signal(false);
|
|
836
|
+
|
|
837
|
+
protected edit(text: string): void {
|
|
838
|
+
this.draft.set(text);
|
|
839
|
+
this.dirty.set(true);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
## Desktop
|
|
845
|
+
|
|
846
|
+
Built for desktop: a centred card over a backdrop, at `sm` 24 rem to `xl` 64 rem wide, or `fullscreen`. By default a backdrop click closes a `dialog` and leaves an `alertdialog` open; `dismissOnBackdrop` overrides either. The page behind is inert and does not scroll, so the mouse wheel scrolls only the dialog body. The close button (×) is a `text` icon button: hover tints it, and its ring shows on keyboard focus only. The footer buttons follow `provideBfDefaults()` like any `bf-button`.
|
|
847
|
+
|
|
848
|
+
## Theming
|
|
849
|
+
|
|
850
|
+
- Card: `bg-surface`, `text-text`, `rounded-card` (`--radius-card`), `shadow-floating`; widths
|
|
851
|
+
24/32/48/64 rem capped at `100vw - 2 × --spacing-gutter`, height capped at
|
|
852
|
+
`100dvh - 2 × --spacing-gutter`. `fullscreen` has no radius or shadow.
|
|
853
|
+
- Title: `text-heading`; body padding `px-6 py-4`, header `px-6 pt-6`, footer `px-6 pb-6`
|
|
854
|
+
with `gap-2` between actions.
|
|
855
|
+
- Close button: a `text` `bf-icon-button` (`sm`) with a lucide `X`.
|
|
856
|
+
- Backdrop: `bf-overlay-backdrop` from the overlay core (a black scrim: 40 % in light mode, 60 %
|
|
857
|
+
in dark).
|
|
858
|
+
- Confirm message: `text-body text-text-secondary`.
|
|
859
|
+
|
|
860
|
+
## Accessibility
|
|
861
|
+
|
|
862
|
+
The overlay panel is `role="dialog"` (or `alertdialog`) with `aria-modal="true"` and
|
|
863
|
+
a name: `ariaLabel` when given; else `aria-labelledby` pointing at the `bf-dialog` title; else
|
|
864
|
+
the first heading in the content; else the `dialog` label ("Dialog"). It never points
|
|
865
|
+
`aria-labelledby` at a missing element, and its ids come from the library's shared counter, so
|
|
866
|
+
they never repeat one in the page. `confirm()` is an `alertdialog` whose
|
|
867
|
+
`aria-describedby` points at the message, with initial focus on the cancel button (the least
|
|
868
|
+
destructive action), else the first button.
|
|
869
|
+
|
|
870
|
+
On open, focus moves into the dialog; Tab and Shift+Tab cycle inside it (the CDK focus trap), and
|
|
871
|
+
a press on the backdrop never moves it out (a `confirm()` stays open with focus on its button); on
|
|
872
|
+
close it returns to the opener (to the topmost dialog still open when the opener is gone). While it is open the page behind it is `inert` (every other child
|
|
873
|
+
of `<body>`; under `provideBfShell()` the page stack) and the document does not scroll. Nested
|
|
874
|
+
dialogs stack: `Escape` closes only the top one. When the body overflows, it becomes focusable
|
|
875
|
+
(`tabindex="0"`, a `role="region"` labelled by the title when there is one), so keyboard users
|
|
876
|
+
can scroll it with the arrow keys. Under
|
|
877
|
+
`prefers-reduced-motion` the dialog cross-fades instead of scaling.
|
|
878
|
+
|
|
879
|
+
### Keyboard
|
|
880
|
+
|
|
881
|
+
| Key | Action |
|
|
882
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
883
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Next / previous focusable element, wrapping inside the dialog |
|
|
884
|
+
| <kbd>Escape</kbd> | Closes the dialog (a confirmation: answers with its cancel button, or nothing) |
|
|
885
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Activates the focused button |
|
|
886
|
+
|
|
887
|
+
## Inputs
|
|
888
|
+
|
|
889
|
+
| Name | Type | Default | Description |
|
|
890
|
+
| ------------- | --------- | ------- | ---------------------------------------------------------------------------------------- |
|
|
891
|
+
| `title` | `string` | `''` | Heading of the dialog, rendered as the `h2` the dialog panel is labelled by. |
|
|
892
|
+
| `closeButton` | `boolean` | `true` | Shows a close button (×) at the top end that closes the dialog with no result. |
|
|
893
|
+
| `closeLabel` | `string` | `''` | Accessible name of the close button; empty uses `provideBfLabels()`'s `close` ("Close"). |
|
|
894
|
+
|
|
895
|
+
## Slots
|
|
896
|
+
|
|
897
|
+
- `header`: More header content after the title, such as a status badge.
|
|
898
|
+
- `default`: The dialog body.
|
|
899
|
+
- `footer`: The actions, aligned at the end.
|
|
900
|
+
|
|
901
|
+
## Notes
|
|
902
|
+
|
|
903
|
+
### Do
|
|
904
|
+
|
|
905
|
+
- Give every dialog a `title`, and word the primary action as the verb ("Save", "Delete").
|
|
906
|
+
- Put the cancel action first in the footer and the primary action last.
|
|
907
|
+
- Use `confirm()` for a yes/no decision and `role: 'destructive'` on the action that loses data.
|
|
908
|
+
- Pass `injector: inject(Injector)` when the opener sits under its own `provideBfLabels()`.
|
|
909
|
+
|
|
910
|
+
### Don't
|
|
911
|
+
|
|
912
|
+
- Don't open a dialog for information that fits inline (a `bf-alert`) or a transient status (a
|
|
913
|
+
toast).
|
|
914
|
+
- Don't nest more than two dialogs; finish or close the first one instead.
|
|
915
|
+
- Don't make a form dialog an `alertdialog` just to block the backdrop; pass
|
|
916
|
+
`dismissOnBackdrop: false`.
|
|
917
|
+
- Don't use `/mobile` sheets or modals on desktop pages; they are phone presentations.
|
|
918
|
+
|
|
919
|
+
## Migration notes
|
|
920
|
+
|
|
921
|
+
### 0.3.0
|
|
922
|
+
|
|
923
|
+
New exported `BfConfirmSize` (`'sm' | 'md'`), the type of `BfConfirmOptions.size`,
|
|
924
|
+
`BfDialogRole` (`'dialog' | 'alertdialog'`), the type of `BfDialogConfig.role`, and
|
|
925
|
+
`BfConfirmButtonRole` (`'cancel' | 'destructive'`), the type of `BfConfirmButton.role`;
|
|
926
|
+
`BfDialogConfig` `autoFocus` takes the new `BfOverlayAutoFocus`.
|
|
927
|
+
|
|
928
|
+
New `bf-dialog` and `BfDialogService` in `@bitforgehq/ui/overlay`: a centred modal dialog for
|
|
929
|
+
desktop and web apps with no Capacitor dependency. `open(component | template, config)` returns
|
|
930
|
+
a typed `BfDialogRef<R, C>` (`afterClosed`, `closed()`, `componentInstance`, `close(result)`);
|
|
931
|
+
the content injects `BfDialogRef` and `BF_DIALOG_DATA`, a template gets `{ $implicit: ref, data }`.
|
|
932
|
+
`BfDialogConfig`: `data`, `size` (`sm` 24, `md` 32 (default), `lg` 48, `xl` 64 rem, capped at the
|
|
933
|
+
viewport minus the gutters, or `fullscreen`), `role` (`dialog`/`alertdialog`), `ariaLabel`,
|
|
934
|
+
`ariaDescribedBy`, `dismissOnBackdrop` (default true for `dialog`, false for `alertdialog`),
|
|
935
|
+
`dismissOnEscape`, `autoFocus`, `restoreFocus`, `panelClass`, `injector`. `bf-dialog` lays out a
|
|
936
|
+
`title` (the `h2` the panel is labelled by), `[bfSlot=header]`, a scrolling body (focusable only
|
|
937
|
+
while it overflows, then a region named by the title when there is one) and an end-aligned
|
|
938
|
+
`[bfSlot=footer]`, with a close button (`closeButton`, `closeLabel`, default the `close` label)
|
|
939
|
+
last in the tab order. `confirm({ title, message?, buttons?, size?, injector? })` opens an alert
|
|
940
|
+
dialog and resolves with the pressed button's `id ?? label` (default buttons Cancel and OK, ids
|
|
941
|
+
`cancel` and `ok`); focus starts on the cancel button, `Escape` answers with it or is ignored
|
|
942
|
+
without one. A navigation to another URL path closes a dialog with no result and a `confirm()`
|
|
943
|
+
with the cancel button's answer (else `undefined`); `closeOnNavigation: false` (on `BfDialogConfig`
|
|
944
|
+
and `confirm()`) keeps it open. Title and message ids come from the library's shared id counter,
|
|
945
|
+
so they never repeat an id of a `bf-dialog` rendered in the page. Focus is trapped and restored,
|
|
946
|
+
the page behind is `inert` and document scroll is locked (APG dialog (modal) and alert dialog
|
|
947
|
+
patterns).
|
|
948
|
+
|
|
949
|
+
JSDoc of `dialog-ref.ts` for the API reference: `@summary`/`@since` on the exported symbols;
|
|
950
|
+
`@param` docs. No behaviour change.
|
|
951
|
+
|
|
952
|
+
JSDoc of `dialog.component.ts` for the API reference: `@slot` docs for `header`, `default`,
|
|
953
|
+
`footer`. No behaviour change.
|
|
954
|
+
|
|
955
|
+
JSDoc of `dialog.service.ts` for the API reference: `@summary`/`@since` on the exported symbols;
|
|
956
|
+
`@default` on the option properties the code applies; `@param` docs. No behaviour change.
|
|
957
|
+
|
|
958
|
+
The `bf-dialog` host carries `data-bf-dialog-id` (its title id, also without a rendered title), so
|
|
959
|
+
`BfDialogHarness` finds the overlay panel the card sits in, also under a stacked dialog.
|
|
960
|
+
|
|
961
|
+
The docs say that Android back in a browser with `CloseWatcher` closes the dialog like `Escape`
|
|
962
|
+
(and that `dismissOnEscape: false` swallows it); see `### overlay core` and Migration 35.
|