# ModalService
## Description
BitModalService shows a BitModal with any content - a component, a Type or markup - from anywhere in the app: a service, a view model, a handler. Each Show hands back a BitModalReference that closes the modal, completes with its answer and says whether the user dismissed it. A BitModalParameters customizes the modal around the content with the same options as BitModal, plus a close guard and a navigation policy.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| OnAddModal | `event Func<BitModalReference, Task>?` | | Raised for every modal shown through this service, whoever showed it. |
| OnCloseModal | `event Func<BitModalReference, Task>?` | | Raised once for every modal of this service that closes, however it closed: answered or closed by the application, dismissed by the user, closed by a navigation or by its container going away. |
| IsContainerAvailable | `bool` | | Whether a BitModalContainer is currently mounted for this service, i.e. whether a Show call right now would actually render its modal. It reflects live state rather than registration in DI. |
| OpenModals | `IReadOnlyList<BitModalReference>` | | A snapshot of the modals this service currently has open, in the order they were opened. It holds what the mounted container renders, plus the persistent modals that are still waiting for a container to mount. |
| GetModal | `BitModalReference? (string? id)` | | The open modal with the given id, or null when there is none - it was closed, or the id belongs to another service. |
| Close | `Task (BitModalReference modal)` | | Closes an already opened modal using its reference, with a null result. This is the application closing the modal, so the CanClose guard is not asked. |
| Close | `Task (BitModalReference modal, object? result)` | | Closes an already opened modal using its reference, with the result its Result task completes with. The CanClose guard is not asked. |
| TryClose | `Task<bool> (BitModalReference modal, object? result)` | | Asks a modal to close and reports whether it did: a modal whose CanClose guard turns the close down stays open and this answers false. |
| CloseAll | `Task` | | Closes every modal this service currently has open, each with a null result, the last one opened first - so each hands the focus back to the modal under it, and the last to the page. The CanClose guards are not asked. |
| Refresh | `Task (BitModalReference? modal)` | | Re-renders the open modals, invalidating their memoized merged parameters. Call it after mutating modal parameters in place, which doesn't change any object reference and is therefore not detected on its own. Without an argument it refreshes every open modal. |
| Show | `Task<BitModalReference> (Dictionary<string, object>? parameters)` | | Shows a new BitModal with a custom component with parameters as its content. |
| Show | `Task<BitModalReference> (BitModalParameters? modalParameters)` | | Shows a new BitModal with a custom component as its content with custom parameters for the modal. |
| Show | `Task<BitModalReference> (Dictionary<string, object>? parameters, BitModalParameters? modalParameters, bool persistent)` | | Shows a new BitModal with a custom component as its content with custom parameters for the custom component and the modal. A persistent modal survives a container remount and is injected into the next container that mounts. |
| Show | `Task<BitModalReference> (Type componentType, Dictionary<string, object>? parameters, BitModalParameters? modalParameters, bool persistent)` | | Shows a new BitModal with a component whose type is only known at run time as its content, for the callers that pick their content from a map or a route. Throws an ArgumentException for a type that is not a Blazor component. |
| Show | `Task<BitModalReference> (RenderFragment content, BitModalParameters? modalParameters, bool persistent)` | | Shows a new BitModal with the given markup as its content, for the content that is not worth a component of its own. The reference's Content stays null for such a modal, since markup is not a component instance. |
| Show | `Task<BitModalReference> (RenderFragment<BitModalReference> content, BitModalParameters? modalParameters, bool persistent)` | | Shows a new BitModal with markup built from the modal's own reference as its content, so the markup can close and answer the modal it is in: modal => @<BitButton OnClick="modal.Close">Got it</BitButton>. |
| Show | `Task<BitModalReference> (Func<BitModalReference, Dictionary<string, object>?> parametersFactory, BitModalParameters? modalParameters, bool persistent)` | | Shows a new BitModal, building the content component's parameters from a factory that receives the modal reference. Use this overload when a parameter needs the reference itself, such as an OnClose callback that closes this very modal. |
## Sub Classes
### BitModalReference Properties
The handle a Show call hands back: what the modal is, what it answered with, and the ways to close it.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Id | `string` | | The unique id of the shown modal. |
| Content | `object?` | null | The instance of the component rendered as the content of the modal. It is captured while the modal is rendered, which is after the Show call returns, so it is still null immediately afterwards. |
| IsClosed | `bool` | false | Whether this modal has already been closed. A reference is never reused, so once set it stays set. |
| IsDismissed | `bool` | false | Whether the modal was closed by the user - the close button, the overlay, the Escape key - rather than by the application. It is what tells a modal that was walked away from apart from one answered with nothing, which the Result alone cannot. |
| Persistent | `bool` | false | Whether the modal survives a container remount and is injected into the next container that mounts. |
| Parameters | `BitModalParameters?` | null | The parameters the modal is shown with, before they are merged with the container's own. |
| Result | `Task<object?>` | | Completes when the modal is closed, with the value it was closed with - null for a modal that was dismissed rather than answered. |
| Rendered | `Task<bool>` | | Completes with true once a container has rendered the modal, and with false for a modal that was closed before it ever rendered - one shown while no container was mounted, or closed in the same breath it was shown. |
| Close | `Task` | | Closes the modal without a result. The CanClose guard is not asked. |
| CloseWith | `Task (object? result)` | | Closes the modal with the given result, which is what its Result task completes with. The CanClose guard is not asked. |
| TryClose | `Task<bool> (object? result)` | | Asks the modal to close and reports whether it did: a modal whose CanClose guard turns the close down stays open and this answers false. |
| Dismiss | `Task<bool>` | | Closes the modal as a dismissal - the way the close button, the overlay and the Escape key close it - which asks the CanClose guard and marks the reference as dismissed. The content's own cancel action. |
| Update | `Task (BitModalParameters? parameters)` | | Replaces the parameters the modal is shown with and re-renders it. The whole set is replaced rather than merged. |
| Update | `Task (Action<BitModalParameters> change)` | | Changes some of the parameters and re-renders the modal, leaving the others as they are: modal.Update(p => p.HeaderText = "Step 2"). The change is applied to a copy, so a set shared between showings is never changed under the other modals. |
| GetResult<T> | `Task<T?>` | | The result the modal was closed with, cast to T - the type's default for a modal that was dismissed or answered with something else. |
| GetContentAsync<T> | `Task<T?>` | | The component rendered as the content, cast to T, waiting for the modal to be rendered first. The type's default for a modal that never rendered or whose content is markup. |
### BitModalContainer Properties
The component that renders the modals of the service. Mount one, in the layout - or a container of your own derived from it, which keeps all of this and adds markup of its own (see BitModalContainerBase).
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| ModalParameters | `BitModalParameters` | new() | The defaults of every modal this container renders - the house style: a maximum width, a close button, a position. The parameters of one showing win over them, and they win over a BitModalParams cascaded by a BitParams. |
| Service | `BitModalService?` | null | The service this container renders the modals of, in place of the one registered in DI - for a region with modals of its own. Read when the container initializes; give the container a @key to switch it. |
### BitModalContainerBase<TReference, TParameters> Properties
The base of BitModalContainer: it attaches to the service, keeps the list of modals, closes them on navigation and when it goes away, and keeps a closed modal in the page until its exit animation has played. A container of your own for a BitModalService derives from BitModalContainer, which already names the service, merges the parameters and honours CloseOnNavigation, and writes only its markup; one for a service of a reference and parameters type of your own derives from this base and writes the three members marked abstract and virtual as well. What a derived container uses:
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| RenderModals | `RenderFragment ()` | | Renders every modal, in the order they were opened, with everything it expects from its container: the reference, the merged parameters and what lets it play its exit animation. Each is keyed by its Id and renders only when something of its own changes - its parameters, a Refresh, its closing - not with every render of the container. A container that cascades the reference and the parameters by hand instead takes a closed modal out of the page at once, with no exit animation. |
| RenderModals | `RenderFragment (RenderFragment<BitModalContainerItem<TReference>> template)` | | Renders every modal the same way, each inside markup of your own: the template is rendered once per modal, renders the modal itself through the Modal of the item it is handed - RenderModals(item => @<div>@item.Modal</div>) - and needs no @key - the container keys it. The template follows every render of the container; the modal inside it still renders only when it changes. |
| ModalReferences | `IReadOnlyList<TReference>` | | The modals this container renders, in the order they were opened - a closed one still playing its exit animation included. |
| GetMergedParameters | `TParameters? (TReference modalRef)` | | The merged parameters of the given modal, cached until the container's ModalParameters change or Refresh is called. |
| ModalService | `BitModalServiceBase<TReference, TParameters>` | | Abstract: the service this container renders the modals of. Read when the container initializes and not to be changed afterwards. |
| MergeParameters | `TParameters? (TParameters? modalParameters, TParameters? containerParameters)` | | Abstract: merges the parameters of one showing, which win, with the container's ModalParameters - BitModalParameters.Merge for a BitModalService. |
| GetCloseOnNavigation | `bool? (TReference modalRef)` | null | Virtual: whether the given modal closes when the path changes, or null for the default, which closes every modal but a persistent one. Read it off the parameters the modals are shown with - BitModalContainer reads BitModalParameters.CloseOnNavigation off the merged ones - or the modals ignore what they ask for. |
### BitModalContainerItem<TReference> Properties
One modal of a container that puts markup of its own around each modal: the context of the template handed to RenderModals.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Reference | `TReference` | | The reference of the modal - closed while it plays its exit animation, which is the only time a container holds a closed one. |
| Modal | `RenderFragment` | | The modal itself, with everything it expects from its container: render it once, where it goes. |
### BitModalParameters Properties
The options a modal is shown with. Every parameter of BitModal - including Class, Style, Dir, AriaLabel and Disabled - has a nullable counterpart here (null means "not set": the container's value, then a BitParams default, then the modal's own default is used), plus the two options only a service can offer:
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| CanClose | `Func<Task<bool>>?` | null | Asked before the user closes the modal - the close button, the overlay, the Escape key - and by TryClose and Dismiss. Answering false keeps the modal open. Close, CloseWith, CloseAll and a close on navigation are the application closing the modal and do not ask it. A guard on the container's parameters is the default for every modal; one of the modal's own is asked instead. |
| CloseOnNavigation | `bool?` | null | Whether the modal closes when the app navigates somewhere else, which it does by default (a persistent modal only closes when this is set to true). Only a change of path counts; a query string or a fragment changed on the same page does not. Set it to false for the modals that outlive a route change. |
### BitModalContentParameters<TComponent> Properties
The parameters of the content component, named by its own properties instead of strings. It is a Dictionary<string, object>, so it goes wherever a Show overload takes the content's parameters.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Add<TValue> | `void (Expression<Func<TComponent, TValue>> parameter, TValue value)` | | Adds the value of one parameter, named as c => c.Parameter - the collection initializer form is { c => c.Parameter, value }. Throws an ArgumentException for an expression that names no [Parameter] property of TComponent, or a value its type cannot take. |
## Examples
\n**Basic**:
```razor
@* in the layout *@
Show
With header & footer
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private async Task ShowModal()
{
// no header to name the dialog, so it is given an accessible name of its own
await modalService.Show(new BitModalParameters { AriaLabel = "Hello from the service" });
}
private async Task ShowChromeModal()
{
await modalService.Show(new BitModalParameters
{
MaxWidth = "32rem",
HeaderText = "Shown by the service",
ShowCloseButton = true,
FooterText = "The footer of the modal."
});
}
// the same modal, with the content type only known at run time
private async Task ShowModalByType(Type contentType)
{
await modalService.Show(contentType, modalParameters: new BitModalParameters { AriaLabel = "Hello from the service" });
}
```
`ModalContent.razor`:
```razor
Hello from the service
This content reaches its own modal through the cascaded BitModalReference.
Ok
Cancel
@code {
[CascadingParameter] private BitModalReference modalReference { get; set; } = default!;
}
```
`ModalBodyContent.razor`:
```razor
This content knows nothing of the modal around it: the chrome comes from the parameters it was shown with.
```
\n**Markup as content**:
```razor
Show markup
Markup that closes itself
@code {
// a template taking the modal's own reference, so the markup can close it
private async Task ShowClosingMarkupModal()
{
await modalService.Show(modal => @
This markup was handed the reference of its own modal.
Got it
, new BitModalParameters { AriaLabel = "Markup that closes itself" });
}
}
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private async Task ShowMarkupModal()
{
await modalService.Show(builder => builder.AddContent(0, "This modal was shown with markup rather than with a component of its own."),
new BitModalParameters { MaxWidth = "28rem", HeaderText = "Markup", ShowCloseButton = true });
}
// in a .razor file, markup is written as a template
private async Task ShowTemplateModal()
{
await modalService.Show(@A line of text, shown as it is. );
}
```
\n**Awaiting a result**:
```razor
Delete the project
works too
var modal = await modalService.Show(new BitModalContentParameters
{
{ c => c.Question, "Delete the project?" }
}, new BitModalParameters { AriaLabel = "Delete the project?", IsAlert = true });
var confirmed = await modal.GetResult();
confirmAnswer = modal.IsDismissed ? "dismissed" : $"{confirmed}";
StateHasChanged();
}
```
`ConfirmModalContent.razor`:
```razor
@Question
Yes
@* the least destructive answer takes the focus when the modal opens *@
No
@code {
[CascadingParameter] private BitModalReference modalReference { get; set; } = default!;
[Parameter] public string? Question { get; set; }
}
```
\n**Reaching the content**:
```razor
Show and count
(new BitModalContentParameters
{
{ c => c.Question, "How long is this question?" }
}, new BitModalParameters { AriaLabel = "How long is this question?" });
// The content is only instantiated once the container renders the modal, so it is waited for rather
// than read straight off the reference the Show call handed back.
var content = await modal.GetContentAsync();
contentReport = $"{content?.Question?.Length ?? 0} characters";
StateHasChanged();
}
// The other direction: the reference is handed to the factory before the content is built, so a
// parameter of the content can be built from it - a callback that closes this very modal, or its id.
private async Task ShowSelfNamingModal()
{
await modalService.Show(modalRef => new BitModalContentParameters
{
{ c => c.Question, $"Close modal {modalRef.Id}?" }
}, new BitModalParameters { AriaLabel = "Close the modal?" });
}
```
\n**Guarding the close**:
```razor
Rename the project
TryClose it from here
(
new BitModalContentParameters
{
{ c => c.HasChangesChanged, EventCallback.Factory.Create(this, v => hasUnsavedChanges = v) }
},
new BitModalParameters
{
// Modeless only so the TryClose button of the page stays reachable while the modal is open;
// the guard is asked the same on a modal that holds the page.
Modeless = true,
ShowCloseButton = true,
HeaderText = "Rename the project",
CanClose = GuardTheClose
});
}
// The guard reports what it answered, so a dismissal it turns down is visible as something having happened.
private Task GuardTheClose()
{
var canClose = hasUnsavedChanges is false;
guardReport = canClose ? "let through" : "turned down (unsaved change)";
StateHasChanged();
return Task.FromResult(canClose);
}
private async Task TryCloseGuardedModal()
{
if (guardedModal is null || guardedModal.IsClosed)
{
guardReport = "nothing open";
return;
}
await guardedModal.TryClose();
}
```
`UnsavedModalContent.razor`:
```razor
@if (hasChanges)
{
Unsaved change: the close button, Escape and TryClose are turned down until you save or discard.
}
Save
Discard
@code {
[CascadingParameter] private BitModalReference modalReference { get; set; } = default!;
[Parameter] public EventCallback HasChangesChanged { get; set; }
private string? value;
private bool hasChanges;
private async Task OnValueChanged(string? newValue)
{
value = newValue;
hasChanges = true;
await HasChangesChanged.InvokeAsync(true);
}
private async Task Save()
{
hasChanges = false;
await HasChangesChanged.InvokeAsync(false);
await modalReference.CloseWith(value);
}
// Discarding is the way out the guard leaves open: the changes are dropped first, so the close goes through.
private async Task Discard()
{
hasChanges = false;
await HasChangesChanged.InvokeAsync(false);
await modalReference.Dismiss();
}
}
```
\n**Updating an open modal**:
```razor
Show, then update it
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private async Task ShowUpdatingModal()
{
var modal = await modalService.Show(new BitModalParameters
{
MaxWidth = "28rem",
HeaderText = "Saving...",
Blocking = true,
NoDismissOnEscape = true
});
// Standing in for the work: the modal can't be dismissed while it runs, and gets its way out once done.
await Task.Delay(2000);
// only what changes is named: the rest of the set stays as it was shown
await modal.Update(p =>
{
p.HeaderText = "Saved";
p.Blocking = null;
p.NoDismissOnEscape = null;
p.ShowCloseButton = true;
p.FooterText = "Only what changed was named; the rest of the set stayed.";
});
}
// a whole set replaces the one the modal was shown with
private async Task ReplaceTheParameters(BitModalReference modal)
{
await modal.Update(new BitModalParameters { HeaderText = "A new set", ShowCloseButton = true });
}
// mutating the parameters already handed to the modal works too, followed by a Refresh
private async Task RenameTheOpenModal(BitModalReference modal)
{
modal.Parameters!.HeaderText = "A new title";
await modalService.Refresh(modal);
}
```
\n**Persistent modals**:
```razor
Show a persistent modal
Show an ordinary one
@(isDemoContainerMounted ? "Unmount the container" : "Mount the container again")
}
```
```csharp
private readonly BitModalService demoModalService = new();
private bool isDemoContainerMounted = true;
private BitModalReference? persistentModal;
private BitModalReference? ordinaryModal;
private async Task ShowPersistentModal()
{
persistentModal = await demoModalService.Show(
new BitModalParameters { MaxWidth = "24rem", HeaderText = "Persistent", ShowCloseButton = true, Modeless = true, Position = BitPosition.TopStart },
persistent: true);
}
private async Task ShowOrdinaryModal()
{
ordinaryModal = await demoModalService.Show(
new BitModalParameters { MaxWidth = "24rem", HeaderText = "Ordinary", ShowCloseButton = true, Modeless = true, Position = BitPosition.TopEnd });
}
// Unmounting the container is what tells the two apart: the ordinary modal is closed by the container that
// was rendering it, and the persistent one is only taken off the screen until a container mounts again.
private void ToggleDemoContainer()
{
isDemoContainerMounted = isDemoContainerMounted is false;
}
private string DescribeModal(BitModalReference? modalRef)
{
if (modalRef is null) return "never shown";
if (modalRef.IsClosed) return "closed";
return isDemoContainerMounted ? "open" : "open, waiting for a container";
}
```
\n**Closing on navigation**:
```razor
@* in the layout: every modal of this container outlives a route change unless it says otherwise
*@
Show a modal
Show one that stays
Change the query string (both stay)
Go to another page
(new BitModalParameters
{
MaxWidth = "24rem",
Modeless = true,
ShowCloseButton = true,
Position = BitPosition.TopStart,
HeaderText = "Closes on navigation"
});
}
// the modals that outlive a route change say so themselves
private async Task ShowLingeringModal()
{
lingeringModal = await modalService.Show(new BitModalParameters
{
MaxWidth = "24rem",
Modeless = true,
ShowCloseButton = true,
Position = BitPosition.TopEnd,
HeaderText = "Stays across a route change",
CloseOnNavigation = false
});
}
private void NavigateWithQuery()
{
// The same page, so the modals on it are the modals of the page still being looked at.
navigationManager.NavigateTo($"/components/modalservice?at={DateTime.Now.Ticks}#example8");
}
private void NavigateToAnotherPage()
{
// A different path, which is what closes the modals of the page being left behind.
navigationManager.NavigateTo("/components/modal");
}
private static string DescribeNavigationModal(BitModalReference? modalRef)
{
if (modalRef is null) return "never shown";
return modalRef.IsClosed ? "closed" : "open";
}
```
\n**Tracking open modals**:
```razor
@implements IDisposable
Show one more
Close all
(new BitModalParameters
{
MaxWidth = "20rem",
Modeless = true,
ShowCloseButton = true,
HeaderText = $"Modal {count + 1}",
Position = StackedModalPosition(count)
});
}
private static BitPosition StackedModalPosition(int index) => (index % 5) switch
{
0 => BitPosition.TopStart,
1 => BitPosition.TopEnd,
2 => BitPosition.BottomStart,
3 => BitPosition.BottomEnd,
_ => BitPosition.Center
};
private async Task CloseAllModals()
{
await modalService.CloseAll();
}
// the code that only kept the id finds the modal again
private async Task CloseById(string id)
{
var modal = modalService.GetModal(id);
if (modal is not null)
{
await modal.Close();
}
}
private Task HandleOnAddModal(BitModalReference modalRef)
{
shownCount++;
return InvokeAsync(StateHasChanged);
}
private Task HandleOnCloseModal(BitModalReference modalRef)
{
closedCount++;
return InvokeAsync(StateHasChanged);
}
public void Dispose()
{
modalService.OnAddModal -= HandleOnAddModal;
modalService.OnCloseModal -= HandleOnCloseModal;
}
```
\n**Cascading parameters**:
```razor
@* in an app, around the container in the layout:
*@
Takes the cascade
Its own position
@* a container rendering a service of the example's own, so the cascade reaches only its modals *@
```
```csharp
private readonly BitModalService paramsModalService = new();
private readonly BitModalParams[] modalParams =
[
new()
{
ModeFull = true,
ShowCloseButton = true,
MaxWidth = "26rem",
Position = BitPosition.TopCenter,
}
];
private async Task ShowCascadedModal()
{
await paramsModalService.Show(new BitModalParameters { HeaderText = "Cascaded" });
}
private async Task ShowCascadedOwnModal()
{
await paramsModalService.Show(new BitModalParameters
{
HeaderText = "Own position",
Position = BitPosition.BottomCenter
});
}
```
\n**Custom container**:
```razor
Show at the start
Show at the end
@* in an app, in the layout in place of the BitModalContainer, handed the service registered in DI *@
```
```csharp
private readonly BitModalService customModalService = new();
private async Task ShowCustomContainerModal()
{
await customModalService.Show(new BitModalParameters
{
MaxWidth = "24rem",
HeaderText = "At the start",
ShowCloseButton = true,
Modeless = true,
Position = BitPosition.TopStart
});
}
private async Task ShowOtherCustomContainerModal()
{
await customModalService.Show(new BitModalParameters
{
MaxWidth = "24rem",
HeaderText = "At the end",
ShowCloseButton = true,
Modeless = true,
Position = BitPosition.TopEnd
});
}
```
`DemoModalContainer.razor`:
```razor
@inherits BitModalContainer
External close icon
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private async Task ShowExternalIconModal()
{
await modalService.Show(new BitModalParameters
{
MaxWidth = "32rem",
ShowCloseButton = true,
HeaderText = "External close icon",
CloseIcon = BitIconInfo.Fa("solid xmark")
});
}
```
\n**Style & Class**:
```razor
@* in the layout: the house style of every modal
*@
CSS variables
Styles
```
```csharp
[AutoInject] private BitModalService modalService = default!;
// in the layout
private readonly BitModalParameters houseStyle = new() { Style = "--bit-Modal-radius: 1rem" };
private async Task ShowCssVariablesModal()
{
await modalService.Show(new BitModalParameters
{
ModeFull = true,
ShowCloseButton = true,
Position = BitPosition.TopCenter,
HeaderText = "CSS variables",
FooterText = "Restyled without a single class.",
Style = "--bit-Modal-background: #1e1b4b; --bit-Modal-color: #e0e7ff; --bit-Modal-border-color: #a5b4fc; --bit-Modal-radius: 1rem; --bit-Modal-padding: 1.5rem; --bit-Modal-offset: 2rem; --bit-Modal-max-width: 30rem; --bit-Modal-overlay-background: #1e1b4b99; --bit-Modal-overlay-backdrop-filter: blur(4px);"
});
}
private async Task ShowStyledPartsModal()
{
await modalService.Show(new BitModalParameters
{
MaxWidth = "32rem",
ShowCloseButton = true,
HeaderText = "Styled parts",
Styles = new()
{
Overlay = "background-color: #4776f433;",
Content = "box-shadow: 0 0 1rem tomato;",
Header = "color: tomato;"
}
});
}
```
\n**RTL**:
```razor
باز کردن مُدال
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private async Task ShowRtlModal()
{
await modalService.Show(builder => builder.AddContent(0, "لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است."),
new BitModalParameters
{
Dir = BitDir.Rtl,
MaxWidth = "30rem",
ShowCloseButton = true,
Position = BitPosition.TopStart,
HeaderText = "لورم ایپسوم",
CloseButtonTitle = "بستن"
});
}
```
Answer: [@confirmAnswer]
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private string confirmAnswer = "-";
private async Task ShowConfirmModal()
{
// the content's parameters, checked by the compiler; a DictionaryThe content reported: [@contentReport]
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private string contentReport = "-";
private async Task ShowContentReachingModal()
{
var modal = await modalService.ShowLast attempt: [@guardReport]
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private bool hasUnsavedChanges;
private string guardReport = "-";
private BitModalReference? guardedModal;
private async Task ShowGuardedModal()
{
hasUnsavedChanges = false;
guardReport = "-";
guardedModal = await modalService.ShowContainer: [@(isDemoContainerMounted ? "mounted" : "unmounted")]
Persistent modal: [@DescribeModal(persistentModal)]
Ordinary modal: [@DescribeModal(ordinaryModal)]
@* An app mounts one container, in its layout: this one renders a service of the example's own,
so that unmounting it leaves the modals of the rest of the page alone. *@
@if (isDemoContainerMounted)
{
Modal: [@DescribeNavigationModal(navigationModal)]
Modal that stays: [@DescribeNavigationModal(lingeringModal)]
```
```csharp
[AutoInject] private BitModalService modalService = default!;
[AutoInject] private NavigationManager navigationManager = default!;
private BitModalReference? navigationModal;
private BitModalReference? lingeringModal;
private async Task ShowNavigationModal()
{
navigationModal = await modalService.Show
Open: [@modalService.OpenModals.Count] Shown: [@shownCount] Closed: [@closedCount]
Container mounted: [@modalService.IsContainerAvailable]
```
```csharp
[AutoInject] private BitModalService modalService = default!;
private int shownCount;
private int closedCount;
protected override void OnInitialized()
{
modalService.OnAddModal += HandleOnAddModal;
modalService.OnCloseModal += HandleOnCloseModal;
base.OnInitialized();
}
// shown Modeless and in a corner so the page stays reachable and every modal of the stack stays visible
private async Task ShowStackedModal()
{
var count = modalService.OpenModals.Count;
await modalService.ShowOpen in this container: [@ModalReferences.Count(m => m.IsClosed is false)]
@(RenderModals(item => @@item.Modal
))
```
\n**External Icons**:
```razor