Blazorise icon indicating copy to clipboard operation
Blazorise copied to clipboard

OnKeyDown in Modal

Open NCBR opened this issue 2 years ago • 8 comments

Hey, I've got a bit of an issue here

So I have a ModalService set up, that opens a custom component. That all works fine. My issue is that when I have ANY OnKeyDown or OnKeyUp events in any of the child content, if escape is pressed it will close the modal. Without any OnKeyDown or OnKeyUp in the child content, pressing escape actually does nothing, doesn't even trigger the closing sequence.

I have an OnKeyDown on my datagrid, which I want to close the row editor if it is in Edit or New state when the Escape button is pressed. What happens instead is that the entire modal closes. I would like to be able to use stopPropagation so I can stop it from continuing out further, but that causes the website to become unresponsive (can't have two OnKeyDown).

I do still want to close the modal if Escape is pressed while not focusing the datagrid.

It seems to me that there is some issue with the handling of the closing sequence, as the modal closing sequence doesn't trigger for me without a OnKeyDown/OnKeyUp sequence present, whether it actually is meant to close it. Any old one will do apparently.

I know I can cancel the closing sequence, but that is also not going to solve my specific issue, as I still want it to close with Escape, just not by Escape being pressed while the datagrid is selected.

Edit: I may be able to provide some code snippets if needed, but due to it being a project for work I do have some limitations in what I can show.

NCBR avatar Sep 08 '23 08:09 NCBR

Hello, Please provide a simple reproducable.

David-Moreira avatar Sep 08 '23 08:09 David-Moreira

Hello @NCBR, thank you for your submission. The issue was labeled "Status: Repro Missing", as you have not provided a way to reproduce the issue quickly. Most problems already solve themselves when isolated, but we would like you to provide us with a reproducible code to make it easier to investigate a possible bug.

github-actions[bot] avatar Sep 08 '23 08:09 github-actions[bot]

DataEntryModalExample.razor @ using System.ComponentModel.DataAnnotations;

Test
<DataGrid @ref="@_dataGrid" TItem="ExampleEntry"
          Data="@_entries"
          @bind-SelectedRow="@_selectedEntry"
          Responsive Editable UseValidation SubmitFormOnEnter Navigable @onkeydown="@(async (e) => await CancelRow(e))">
    <DataGridColumns>
        <DataGridColumn Sortable Field="@nameof(ExampleEntry.Value)" Caption="Value" Editable DisplayFormat="{0:0.####}" VerticalAlignment="VerticalAlignment.Middle" DisplayFormatProvider="@System.Globalization.CultureInfo.GetCultureInfo("da-DK")">
            <EditTemplate>
                <NumericPicker @ref="@_numericPicker" TValue="float?" Value="@((float?)context.CellValue)" AlternativeDecimalSeparator="," Decimals="4" Min="0.0000f"  Max="10000.0000f" RoundingMethod="NumericRoundingMethod.HalfEvenBankersRounding" ValueChanged="@( v => context.CellValue = v)"  Autofocus SelectAllOnFocus/>
            </EditTemplate>
            <DisplayTemplate>
                @{
                    @($"{String.Format(CultureInfo.CreateSpecificCulture("da-DK"), "{0:0.00##}", context.Value)}")
                }
            </DisplayTemplate>
        </DataGridColumn>
    </DataGridColumns>
</DataGrid>

@ code { [Inject] public IModalService ModalService { get; set; }

private List<ExampleEntry> _entries = new List<ExampleEntry>();

private ExampleEntry _selectedEntry;

private DataGrid<ExampleEntry> _dataGrid;
private NumericPicker<float?> _numericPicker;

private async Task StartNew()
{
    await _dataGrid.New();
}

protected override void OnInitialized()
{
    this.ModalService.ModalProvider.Closing = OnModalClosing;
}

private async Task Confirm()
{
    if (_dataGrid.EditState == DataGridEditState.None)
    {
        await ModalService.Hide();
    }
}

public async Task CancelRow(KeyboardEventArgs e)
{
    if (!e.Repeat && e.Code == "Escape" && _dataGrid.EditState != DataGridEditState.None)
    {
        await _dataGrid.Cancel();
    }
}

private Task OnModalClosing(ModalClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.FocusLostClosing || e.CloseReason == CloseReason.None || e.CloseReason == CloseReason.EscapeClosing)
    {
        if (_dataGrid.EditState != DataGridEditState.None)
        {
            e.Cancel = true;
        }
    }

    return Task.CompletedTask;
}

public class ExampleEntry
{
    [Required]
    [Range(0.0000f, 10000.0000f)]
    public float? Value { get; set; }
}

}

DashboardModalExample.razor @ page "/testmodal"

<Button Color="Color.Info" Clicked="ShowModal">Open Modal</Button>

@ code { [Inject] public IModalService ModalService { get; set; }

public Task ShowModal()
{
    return ModalService.Show<DataEntryModalExample>(x =>
    {
    },
    new ModalInstanceOptions()
        {
            UseModalStructure = false,
            Size = ModalSize.ExtraLarge,
            FocusTrap = true
        });
}

}

When datagrid is in new/edit state here, it cancels. But if you put an onkeydown event for escape key on the datagrid that then cancels the line, and subsequently closes the entire modal, instead of only cancelling the line.

NCBR avatar Sep 08 '23 09:09 NCBR

Hello @NCBR I just tried your example. Seems like a race condition. Stuff get's executed asynchronously, and the CancelRow actually ends up executing first, so the DataGrid won't be on an Edit State when OnModalClosing is called.

Maybe try adding a flag to figure out if the row was already cancelled? Can you try something like this?

    public async Task CancelRow( KeyboardEventArgs e )
    {

        if (!e.Repeat && e.Code == "Escape" && _dataGrid.EditState != DataGridEditState.None)
        {
            _cancelledRow = true;
            await _dataGrid.Cancel();
        }
    }

    private bool _cancelledRow = false;
    private Task OnModalClosing( ModalClosingEventArgs e )
    {
        if (e.CloseReason == CloseReason.FocusLostClosing || e.CloseReason == CloseReason.None || e.CloseReason == CloseReason.EscapeClosing)
        {
            if (_dataGrid.EditState != DataGridEditState.None || _cancelledRow)
            {
                e.Cancel = true;
                _cancelledRow = false;
            }
        }

        return Task.CompletedTask;
    }

David-Moreira avatar Sep 11 '23 16:09 David-Moreira

I tried your suggestion and it did work. Now the only problem is if I click out of the datagrid with my mouse (so I click anywhere on the modal), the modal doesn't seem to respond to escape being pressed.

If I press escape while the datagrid is selected, it correctly cancels the row. I can then press escape again to close the modal.

But if I click anywhere on the modal, it does not respond to escape until I select the datagrid again.

Honestly not sure why it does that, or whether it's just on my end.

NCBR avatar Sep 12 '23 08:09 NCBR

Glad it worked. As for your current issue, I suspect, It might be because it thinks the focus is elsewhere and the escape is not triggered on the modal itself. But we'll have to take a look to make sure.

We'll let you know once we have news again.

David-Moreira avatar Sep 12 '23 08:09 David-Moreira

@David-Moreira, should we close this, or do you think we can add some extra logic to the codebase that handles this use case?

stsrki avatar Jul 30 '24 09:07 stsrki

We can take a look just to be sure.

David-Moreira avatar Jul 30 '24 22:07 David-Moreira