Listening to "esc" key event on div component in Vue.js
Asked Answered
C

6

71

I wish to listen to an 'esc' key event in order to call a method in a Vue component. The docs shows this example:

<input v-on:keyup.enter="submit">

but i'm using a <div></div>, and need to catch the event from outside. However, I wish NOT to overload global handlers or anything like that.

Any suggestions?

Crossindex answered 8/8, 2017 at 12:48 Comment(3)
did you find your solution for this ? I'm trying to do the same !Verdaverdant
What is your end game here? I don't think there is such event as @keydown on non-input elements, especially on div. Can you please clarify what exactly do you have and what exactly needs to happen after you press ESC key?Dorseydorsiferous
I want a modal to close @DorseydorsiferousLurdan
C
102

For anyone who wanders here from google, in Vue 2...

<div @keydown.esc="something_in_your_methods"></div>
Contrabassoon answered 30/1, 2018 at 2:27 Comment(4)
thanks, solve the problem but only on <input> or <textarea>.Ledet
@MosheL It works for me on a <div>. I'm using Vue version 2.5.17Companionable
This will only work placed on a div if you have a nested element that will capture the keystroke like an input, in which case the event can bubble up. div alone will not capture the event.Corpora
Or if you add a tabindex to the div like tabindex="0"Terrazas
C
55

The secret for making keydown events work on divs and other non-focusable elements is to add a tabindex attribute:

<div tabindex="0"
    @keydown.left="previousImage"
    @keydown.right="nextImage" />

Now the div has become a focusable element and the key events will be triggered.

Here is more info on focusable elements and tabindex

Cleland answered 19/5, 2019 at 16:49 Comment(3)
Isn't that potentially bad for accessibility?Trial
Use this on places where it makes sense. I personally have a table with rows. Any row can be clicked on to open a window. I want to make it accessible using keyboard so I'd argue it's actually good for accessibilitySoldier
@Trial This article might answer your questionWeka
H
25

What I did was go for a mixin.

The mixin in a file called close.js

export default {
    created() {
        let that = this;

        document.addEventListener('keyup', function (evt) {
            if (evt.keyCode === 27) {
                that.close();
            }
        });
    },
};

Import and use it in the desired component

import closeMixin from './../../mixins/close.js';

export default {
    mixins: [closeMixin],
    props: [],
    computed: {}
    methods: {
        close(){
            // closing logic
        }
    }
}
Hover answered 8/4, 2019 at 9:46 Comment(3)
But, you forgot to remove the event listener on destroy, wouldn't this have unintended effects?Bartz
store the in a variable e.g. this.escListener = document.addEventListener('keyup' then in the destroy() lifecycle method document.removeEventListener(this.escListener)Affidavit
As of 2022 if (evt.key === "Escape") { is the preferred (non-deprecated) expression.Kershner
C
18

3 things to make sure of on the main element:

  • It has a tabindex
  • It or a descendant is focused
  • It is listening for the event

Here is how I usually manage my modals:

<div ref="modal" @keyup.esc="close" tabindex="-1">
   <!-- Modal content -->
</div>
mounted() {
    this.$refs.modal.focus();
}
Capillaceous answered 2/11, 2021 at 9:56 Comment(0)
L
7

In my case, I created a directive, v-esc.ts. (※ This is Vue3 directive writing way)

import { Directive } from 'vue'
const directive: Directive = {
  beforeMount(el, binding) {
    el._keydownCallback = (event) => {
        if (event.key === 'Escape') {
            binding.value()
      }
    }
    document.addEventListener('keydown', el._keydownCallback)
  },
  unmounted(el, binding) {
    document.removeEventListener('keydown', el._keydownCallback)
    delete el._keydownCallback
  }
}
export const esc = { esc: directive }

Then I can use it in any component like this. (NOTE: you must pass a function param to v-esc, because the param executed as binding.value() in the directive)

<template>
  <img
    @click.prevent="close"
    v-esc="close"
    src="@/assets/icons/close.svg"
  />
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { esc } from '@/common/directives/v-esc'
export default defineComponent({
  name: 'nitCloseButton',
  ...
  methods: {
    close() {
      this.$emit('close')
    }
  },
  directives: {
    ...esc
  }
})
</script>

P.S One month after, I also need arrow left and arrow right keys. So, I've made this directive more general like this.

import { Directive } from 'vue'
const directive: Directive = {
  beforeMount(el, binding) {
    el._keydownCallback = event => {
      console.log('keydown', event.key)
      if (event.key === binding.arg) {
        binding.value()
      }
    }
    document.addEventListener('keydown', el._keydownCallback)
  },
  unmounted(el, binding) {
    document.removeEventListener('keydown', el._keydownCallback)
    delete el._keydownCallback
  }
}
export const keydown = { keydown: directive }

You can detect any key's keydown by passing keyname as binding.args (v-keydown:{keyName} like below)

<button
  v-keydown:ArrowLeft="moveToPreviousPage"
  class="controller-button lo-center"
  @click="moveToPreviousPage"
>
  <arrow-icon :rotation="180" />
</button>
<button
  v-keydown:ArrowRight="moveToNextPage"
  class="controller-button lo-center"
  @click="moveToNextPage"

export default defineComponent({
  name: 'componentName',
  directives: {
    ...keydown
  }
...
})
Lightman answered 26/6, 2021 at 13:18 Comment(1)
Interesting, I haven't tried writing my own directives yet, but this looks quite promising. Also this did work out of the box for me. The only thing my intellisense mentioned is, that the directives property should be close to top (before setup in my case). Thank you @Matsumoto KazuyaAgna
E
5

You can't. Key events are dispatched from the body tag and Vue can't be mounted to the <body> tag.

Sourced from "When VueJS Can't Help You"]

You'll have to set up your own event listener.

(image source & more info at When VueJS Can't Help You)

Erzurum answered 4/12, 2017 at 9:6 Comment(2)
And yet you can: <div @keydown.esc="something"> works fine.Companionable
@AlexejMagura only if the element is in focus. This answer helped me make a dialog esc-closable without the user having to click into it.Laundrywoman

© 2022 - 2024 — McMap. All rights reserved.