Search code examples
blazoraccessibility

Blazor keyboard accessibility: paste for inputFile


I adapted the code from the tutorial here. It allows dragging or pasting files into a Blazor InputFile component. It works, however, unless I first click on the page with the mouse, I cannot use the keyboard shortcut control + v to paste a file. After I click the page with a mouse, the keyboard paste works as expected. It seems I must first “activate” the page with a mouse click.

I am blind and use a screen reader to navigate without a mouse. I asked a sighted person to try the page on their computer and they observed the same behavior, so I don’t think the screen reader is interfering with the process.

When composing a message with Gmail in the browser, I can paste files as attachments with control + v, without needing to first activate the page with a mouse click, so I think the behavior I hope for is possible.

Can someone suggest a way to make the code below work without a mouse?

[Upload.razor]

@page "/Upload"

<h2 style="text-align:center">Upload files</h2>

<div @ref="_fileDropContainer" class="file-drop-zone @_hoverClass" @ondragenter="OnDragEnter" @ondragleave="OnDragLeave" @ondragover="OnDragEnter">
    <InputFile OnChange="@OnChange" @ref="_inputFile" />
    <h1 style="text-align:center">Drag or paste files here</h1>
</div>

<div class="error-message-container">
    <p>@_errorMessage</p>
</div>

@code {

    ElementReference _fileDropContainer;
    InputFile _inputFile;

    IJSObjectReference _filePasteModule;
    IJSObjectReference _filePasteFunctionReference;

    string _hoverClass;
    string _errorMessage;

    override protected async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _filePasteModule = await JS.InvokeAsync<IJSObjectReference>("import", "./js/filePaste.js");
            _filePasteFunctionReference = await _filePasteModule.InvokeAsync<IJSObjectReference>("initializeFilePaste", _fileDropContainer, _inputFile.Element);
        }
    } //afterRender

    async Task OnChange(InputFileChangeEventArgs args)
    {
        try
        {
            //omitted code for writing the file to disk
            _hoverClass = "";
        }
        catch (Exception e)
        {
            // Exception handling omitted for brevity
        }
        this.StateHasChanged();
    } // onChange

    void OnDragEnter(DragEventArgs e) => _hoverClass = "hover";
    void OnDragLeave(DragEventArgs e) => _hoverClass = "";

    //Dispose method omitted
}

[filePaste.js]

export function initializeFilePaste(fileDropContainer, inputFile) {

    function onPaste(e) {
        inputFile.files = e.clipboardData.files;
        const event = new Event('change', { bubbles: true });
        inputFile.dispatchEvent(event);
    }

    fileDropContainer.addEventListener('paste', onPaste);

    return {
        dispose: () => {
            fileDropContainer.removeEventListener('paste', onPaste);
        }
    }
}

Solution

  • Adding event listener to the document itself helped:

    In your filePaste.js:

    //fileDropContainer.addEventListener('paste', onPaste);//change this to 👇
    document.addEventListener('paste', onPaste); 
    

    You have to take care about the consequences. Mainly removing the event listener when you leave the page.