R. Kohlisch
R. Kohlisch

Reputation: 2993

Bind Mousetrap Hotkey to certain DOM element - trigger only when div is active?

I want to bind some hotkeys to a div: Whenever the user clicks somewhere inside the div and then presses the S key I want to console.log something. However, I don't want this hotkey to be global and to be triggered each and every time the user presses S.

Here is what I've got so far:

import React from "react"
import Mousetrap from "mousetrap"

export default class Mouse extends React.Component {

  constructor(props) {
    super(props)
  }

  componentDidMount() {
    let form = document.querySelector("form")
    let m = new Mousetrap(form)

    m.bind("s", () => {
      console.log("s")
    })
  }

  componentWillUnmount() {
    // m.unbind("s", () => {
    //   console.log("s")
    // })
  }
  render() {
    return (  
        <form
          style={{ width: "300px", height: "300px", backgroundColor: "pink" }}
        >
          <input type="text" />
        </form>
    )
  }
}

The Mousetrap docs say that I can bind my mousetrap like so:

var form = document.querySelector('form');
var mousetrap = new Mousetrap(form);
mousetrap.bind('mod+s', _handleSave);
mousetrap.bind('mod+z', _handleUndo);

As you can see in my example above, that's what I've done. It does work in this sense: whenever I type S while I'm in the input of the form, the console.log is being triggered. However, I don't want to use a form and neither do I want my user to be inside an input: I just want my user to have clicked on the div. I cannot get this to work though. I would expect it to look something like this:

import React from "react"
import Mousetrap from "mousetrap"

export default class Mouse extends React.Component {

  constructor(props) {
    super(props)
  }

  componentDidMount() {
    let form = document.querySelector(".trigger")
    let m = new Mousetrap(form)

    m.bind("s", () => {
      console.log("s")
    })
  }

  componentWillUnmount() {
    // m.unbind("s", () => {
    //   console.log("s")
    // })
  }
  render() {
    return ( 
        <div
          className="trigger"
          style={{ width: "300px", height: "300px", backgroundColor: "pink" }}
        >
           Click me!
        </div>
    )
  }
}

However, this doesn't work. Nothing is being triggered.

Edit: Also, one thing I don't quite understand in the first example above is that I am binding Mousetrap to form. However, the s hotkey is only ever triggered when I am inside the input field of form, but never when I just click on the form but not the input.

Upvotes: 0

Views: 510

Answers (2)

trixn
trixn

Reputation: 16344

The reason this happens is that the Mousetrap is checking if the element is focused. divs (or in fact any other block element like a form) can only be focused if they have a tabindex defined. Inputs can be focused without that.

But I believe you do not need to explicitly bind the Mousetrap to the div at all. All you need to do is to track the active state of your div and bind() or unbind() the trap accordingly.

Example:

class Mouse extends Component {
  state = {
    active: false,
  }

  constructor(props) {
    super(props);
    this.trap = new Mousetrap();
  }

  handleClick = () => {
    this.setState(({active}) => {
      if (!active) {
        this.trap.bind('s', this.handleKeyPress);
      } else {
        this.trap.unbind('s');
      }
      return {active: !active}
    })
  }

  handleKeyPress = () => {
    console.log('User pressed S.')
  }

  componentWillUnmount() {
    this.trap.reset();
  }

  render() {
    const {active} = this.state;

    return (
      <div 
        className={cN('trigger', {active})} 
        onClick={this.handleClick}
      >
          Click me!
      </div>
    );
  }
}

Demo:

Edit modest-edison-hdfp7

Upvotes: 2

R. Kohlisch
R. Kohlisch

Reputation: 2993

I think I've found the solution. Giving the div a tabindex makes it selectable, and thus, the hot key will be registered. I am not sure why this is, whether it's strictly necessary or a bit of a hacky solution. So all I had to do is:

<div className="trigger" tabindex="0">

...if anyone has a better explanation that goes in some more depth, feel free to post still, as I won't select this as the final answer for now.

Upvotes: 0

Related Questions