I was running into a similar, if not the same, issue. In my case, I have a shared component from an RCL, that exists as a child component on either a page or other component. I'm creating a custom dropdown menu and I needed a global event that would close all of those components if I clicked anywhere else on the page.
Originally, I resorted to creating a global click event with Javascript. I decided to look back into it and came up with a solution that has zero Javascript and I wanted to share and it's actually pretty simple.
I have my component
<div class="dropdown" @onclick="toggleDropdown" @onclick:stopPropagation>
@if(_item.Expanded)
{
<div class="dropdown-list">
<ul>
<li>Item</li>
</ul>
</div>
}
</div>
@code{
[CascadingParameter]
protected MyService Service { get; set; }
[Parameter]
protected Item Item { get; set; }
private Item _item;
protected override void OnParametersSet()
{
_item = Item;
if(!Service.Items.Contains(_item))
Service.Items.Add(_item);
base.OnParametersSet();
}
private void toggleDropdown() => _item.Expanded = !_item.Expanded;
}
Then I have my service
public class MyService : IMyService
{
private List<Item> _items;
public List<Item> Items
{
get => _items ?? new List<Item>();
set => _items = value;
}
public void CloseDropdowns()
{
if(Items.Any(item => item.Expanded))
{
foreach(var i in Items)
i.Expanded = false;
}
}
}
Finally, I have the MainLayout of my actual application
<CascadingValue Value="MyService">
<div class="page" @onclick="MyService.CloseDropdowns">
<div class="main">
<div class="content px-4">
@Body
</div>
</div>
</div>
</CascadingValue>
Hope this helps.
Update
I failed to mention an issue that occurs when there are multiple of the same component on a page, for example, two or more dropdowns. The above code works as designed since the mainlayout click event is bubbled up, but we don't want this to close everything every time, we could have one dropdown open and all the others closed.
For this, we need to stop the event bubbling to the mainlayout and provide a callback to the dropdown's parent component that will update the child component.
In the service
public void ToggleDropdown(bool expanded, Item item)
{
foreach(var i in _items)
i.Expanded = false;
item.Expanded = expanded;
}
In the component
[Parameter]
public EventCallback<(bool, DSCInputConfig)> ExpandCallback { get; set; }
private bool _shouldRender;
protected override bool ShouldRender() => _shouldRender;
private void toggleDropdown()
{
_expanded = !_expanded;
ExpandCallback.InvokeAsync((_expanded, _config));
_shouldRender = true;
}
Finally, in the parent component/page
<Dropdown Options="@component" ExpandCallback="dropdownCallback" />
<Dropdown Options="@component" ExpandCallback="dropdownCallback" />
@code{
private void dropdownCallback((bool expanded, Item config) item) => MyService.ToggleDropdown(item.expanded, item.config);
}