Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prevent all event propagation in a certain context #31

Closed
kespindler opened this issue Jun 24, 2016 · 8 comments
Closed

prevent all event propagation in a certain context #31

kespindler opened this issue Jun 24, 2016 · 8 comments

Comments

@kespindler
Copy link

Not sure if this is better put in mousetrap or react-hotkeys, but I have the following use case.

I have an input field that I want particular hotkeys to work on, so I have

<HotKeys ...>
    <input type='text' />
</Hotkeys>

However, because I have hotkeys further up the stack that would conflict with entering text in the input, I want to use only hotkeys in the immediately surrounding context, and not forward events up.

Is this possible?

@kespindler
Copy link
Author

kespindler commented Jun 24, 2016

Same issue as #14. I'm resolving for now with the following:

let map = {
  handleEscape: 'esc',
  empty: 'abcdefghijklmnopqrstuvwxyz'.split('')
}
let handlers = {
  handleEscape: this.props.handleEscape,
  empty: function () {}
}

but definitely it feels hacky

@chrisui
Copy link
Contributor

chrisui commented Jul 22, 2016

To try and confirm your requirement are you looking for some sort of "bubble isolation" where you will not trigger any sequences (and therefore handlers) at any parent level above?

Eg. maybe something like

<HotKeys ...> // handlers here would never be called if the nested input was in focus
   <HotKeys isolate={true} ...> // handlers here would be called
       <input type='text' />
   </HotKeys>
</Hotkeys>

As a side note I'm a bit mythed by this since I thought mousetrap would not fire from input events by default as documented here: https://craig.is/killing/mice#api.stopCallback

@JoshuaCWebDeveloper
Copy link

JoshuaCWebDeveloper commented Sep 7, 2016

As a side note I'm a bit mythed by this since I thought mousetrap would not fire from input events by default as documented here: https://craig.is/killing/mice#api.stopCallback

You are correct that that is the default behavior of Mousetrap when bound to the document. However, when bound to a specific element, this behavior does not apply: https://craig.is/killing/mice#wrapping

So the default rules about events not firing inside of a textarea or input do NOT apply when Mousetrap is attached to a specific element.

Interestingly enough, it seems you aren't thrilled about binding to a specific element as suggested by the comment in this code in HotKeys.js:

componentDidMount: function componentDidMount() {
    // import is here to support React's server rendering as Mousetrap immediately
    // calls itself with window and it fails in Node environment
    var Mousetrap = require('mousetrap');
    // Not optimal - imagine hundreds of this component. We need a top level
    // delegation point for mousetrap
    this.__mousetrap__ = new Mousetrap(this.props.attach || _reactDom2.default.findDOMNode(this));

    this.updateHotKeys(true);
  }

If you chose the document as your top-level delegation point, then this issue of inputs triggering the hotkeys would solve itself.

@JoshuaCWebDeveloper
Copy link

It took me about three hours to figure out how I could use Mousetrap.stopCallback as suggested in #14. For anyone else who is also wondering, this is the solution I came up with (it is an obvious hack since I am using a private property defined in HotKeys):

    <HotKeys id="logger" className="row screen" keyMap={this.props.route.LHK} handlers={this.hotkeyHandlers} ref={this.overrideMousetrap}>
        {this.props.children}
    </HotKeys>

    overrideMousetrap (HotKeys) {
        //define previous stopCallback handler for mousetrap
        HotKeys.__mousetrap__.stopCallback = function(e, element, combo) {
            // if the element has the class "mousetrap" then no need to stop
            if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
                return false;
            }
            // stop for input, select, and textarea
            return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
         }
    }

See https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute for a little more info.

@admmasters
Copy link
Contributor

admmasters commented Sep 16, 2016

Interesting - I was looking at a similar (I think from the description) scenario where I want HotKeys to manage my inner components and their state, but prevent certain key events from bubbling up if they are handled by HotKeys. In my case, a user scrolls up and down a table component but in this case I don't want the up and down keys affecting the cursor position in my input box - I resolved this internally, but have been trying to conceptualise a better more generic solution.

@chrisui
Copy link
Contributor

chrisui commented Nov 29, 2016

Thanks for publicizing your solution @JoshuaCWebDeveloper

I will be taking this into consideration when we re-work the api for v1.

NB. Hoping to transfer ownership of this project into my company so we can have an active team maintain it as day-to-day rather than being a neglected project on the side.

@chrisui chrisui closed this as completed Nov 29, 2016
@slorber
Copy link

slorber commented Dec 8, 2016

Thanks @JoshuaCWebDeveloper was looking for the same solution.

Mousetrap does prevent shortcuts on inputs, but it always bypass this prevention if the trap target is a parent of the input:

        if (_belongsTo(element, self.target)) {
            return false;
        }

@slorber
Copy link

slorber commented Dec 8, 2016

edit what works best for me is to handle ref of Hotkeys and assign directly callback to prototype

function stopCallbackFixed(e, element, combo) {
  // if the element has the class "mousetrap" then no need to stop
  if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
    return false;
  }
  // stop for input, select, and textarea
  return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
}
const overrideHotkeysMousetrap = (HotKeys) => {
  if ( HotKeys ) {
    HotKeys.__mousetrap__.__proto__.stopCallback = stopCallbackFixed;
  }
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants