Search code examples
javascriptangulartypescriptdom-eventsonpaste

How do I stop buttons from blocking an onPaste event


I have an image modal where the user can upload or paste an image. Both are working great, except currently I have the buttons on the modal capturing the focus, so pasting only works by manually clicking outside the buttons. I would like for any paste to work if this component has focus, or anything inside the component has focus.

<div onPaste="onPaste()">
  <button class="__cancel" aria-label="Close" onClick="onClickCancel()">
  <button ... upload .../>
</div>

Is there a way to allow the paste action to pass through the buttons?

This is actually an angular application, so below is closer to my actual code:

<div (paste)="onPaste($event)" cdkTrapFocusAutoCapture cdkTrapFocus>
  <button class="__cancel" aria-label="Close" (onClick)="onClickCancel()">
  <button ... upload .../>
</div>

I have tried adding the paste method to the buttons, but they don't fire.

<div (paste)="onPaste($event)" cdkTrapFocusAutoCapture cdkTrapFocus>
  <button class="__cancel" aria-label="Close" (onClick)="onClickCancel()" (paste)="onPaste($event)">
  <button ... upload   (paste)="onPaste($event)".../>
</div>

Thank you


Solution

  • Paste event is intercepted by all HTML elements, but it has effect only on elements that is a editable context, such as <input>s and <textarea>s.
    Elements such as <div>s or <p>s can be receptive to (paste) only if they are able to have content. This is possible by turning on contenteditable:

    <div onPaste="onPaste()" contenteditable="true">
      <button class="__cancel" aria-label="Close" (click)="onClickCancel()">
                                                  //^-here also
      <button ... upload .../>
    </div>
    

    Here is an extract from the API doc about this:

    If the cursor is in an editable context, the paste action will insert clipboard data in the most suitable format (if any) supported for the given context.

    The paste action has no effect in a non-editable context, but the paste event fires regardless.

    Also, because of privacy restrictions, the element should be in focus:

    To help prevent abuse, this API must not be available unless the script is executing in the context of a document that has focus.

    Different browser might have different behaviours, but keeping in mind these restriction might help to make the code work everywhere.

    --- EDITS ---

    Making your buttons contenteditable creates some issues.

    • The user can focus that area, and it will have a cursor. This can be fixed by styling with

      [contenteditable] { caret-color: transparent; }

    • It also gives the user the ability to change their content, which is probably undesired. This can be fixed with by adding a keydown handler:

      <button class="__cancel" aria-label="Close" mat-icon-button onClick="onClickCancel()" onPaste="onPaste($event)" contenteditable="true" onKeydown="preventKey($event)">

    such as:

      preventKey(event: KeyboardEvent) {
        // Buttons need contenteditable to receive (paste),
        // but we don't want our buttons to be editable, so this is blocked by this function.
        if (
          !(
            event.key === 'Tab' ||
            event.keyCode == 9 ||
            ((event.ctrlKey || event.metaKey) &&
              (event.key === 'v' || event.keyCode == 86))
          )
        ) {
          event.preventDefault();
        }
      }
    
    • Pasting to a contenteditable element does not work on safari - unless we style

      user-select: auto;

    If you have an angular project (as in the original question) this breaks the cdkTrapFocus. As far as the author knows, this is a bug in angular. See https://github.com/angular/components/issues/23846