Dialogs
Hosanna dialogs are ordinary Hosanna views hosted by the application dialog manager. Present one from a view with:
this.showDialog(dialog);
Dialogs are placed on a separate navigation stack, focus is locked to that stack, and the previous application focus is restored when the stack empties.
Use the built-in dialog
Dialog provides a title, message, styled button list, and dismissal callback:
const dialog = Dialog({
id: 'deleteConfirmation',
title: 'Delete download?',
message: 'You can download it again later.',
buttonTexts: ['Delete', 'Cancel'],
buttonCallbacks: [
() => this.deleteDownload(),
() => {},
],
onDismissed: () => {
this.setFocusedSubview('downloadsButton');
},
});
this.showDialog(dialog);
Each button callback runs first; the built-in dialog then calls dismiss().
onDismissed runs when the dialog has been removed from its aggregate view,
regardless of which button dismissed it.
Other built-in appearance fields include buttonStyles, buttonGap,
contentWidth, paddingX, paddingY, titleStyleKey,
messageStyleKey, backgroundColor, contentBackgroundColor, and
contentBackgroundUri.
Choose the presentation transition
showDialog(dialog, options) accepts INavigationPresentationOptions for
animation and a named transition:
this.showDialog(dialog, {
animated: true,
transitionStyleKey: 'controls.NavController.transition.slide',
});
| Option | Default | Purpose |
|---|---|---|
animated | true | Runs the selected transition visually. Set it to false to preserve lifecycle and focus behavior without visual animation. |
transitionStyleKey | controls.NavController.transition.default | Uses one named controls.NavController.transition.* configuration for this presentation. |
When transitionStyleKey is omitted, the dialog stack resolves
controls.NavController.transition.default, then Hosanna's internal fade
fallback. Configured Fade and Slide transitions handle the empty-to-first
dialog boundary as well as transitions between dialogs.
Use the options object rather than the old boolean second argument:
this.showDialog(dialog, { animated: false });
present() exposes the same fields when isDialog is set:
this.present(dialog, {
isDialog: true,
animated: true,
transitionStyleKey: 'controls.NavController.transition.slide',
});
See Aggregate View Transitions
for named transition configuration and the available Simple, Fade, and
Slide options.
Build a typed custom dialog
For a custom result, make the dialog view implement IDialogView<T>, store the
selected result, and call its callback from
onDidRemoveFromAggregateView():
export interface ConfirmDialogState extends ViewState {
onDismissed?: DialogDismissedWithValueCallback<boolean>;
}
@view('ConfirmDialog')
export class ConfirmDialogView
extends BaseView<ConfirmDialogState>
implements IDialogView<boolean> {
@state
onDismissed: DialogDismissedWithValueCallback<boolean> = () => {};
private accepted = false;
protected override getViews() {
return [
VGroup([
Label({ text: 'Continue?' }),
Button({ id: 'cancel', text: 'Cancel' })
.isInitialFocus()
.onClick(() => {
this.accepted = false;
this.dismiss();
}),
Button({ id: 'continue', text: 'Continue' })
.onClick(() => {
this.accepted = true;
this.dismiss();
}),
]),
];
}
override onDidRemoveFromAggregateView(owner: IAggregateView): void {
this.onDismissed(this.accepted);
}
}
The callbacks above are created inside the dialog class, so this.dismiss()
correctly refers to the dialog view. A free-standing DSL factory has no dialog
instance to use as this.
Dialog guidelines
- Give the first intended action initial focus and test directional focus inside the modal.
- Put outcome data in a typed
onDismissedcallback rather than reaching into the dialog after removal. - Restore focus in the presenting view when a specific control should regain focus.
- Use the keyboard dialog utility for the framework's keyboard flow; do not recreate its native/platform behavior as a generic dialog.