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

mouseenter fires on disabled inputs whereas mouseleave does not #4251

Closed
jquense opened this issue Jun 29, 2015 · 39 comments
Closed

mouseenter fires on disabled inputs whereas mouseleave does not #4251

jquense opened this issue Jun 29, 2015 · 39 comments

Comments

@jquense
Copy link
Contributor

jquense commented Jun 29, 2015

There is an asymmetry to EnterLeave event plugin. Since mouseenter is created from the relativeTarget of the mouseout event it fires even though the target is disabled. Since the mouseleave is the inverse, i.e requires that the disabled element fire a mouseout, it doesn't fire a mouseleave for the disabled element.

I am pretty sure the correct behavior here is that neither event should fire if its target is disabled, since this mirrors mouseout. No idea if none-chrome browsers have the same behavior for which mouse events fire on disabled elements.

Additional caveat I just realized, React is probably also not firing mousenter events in the case where the mouse leaves a disabled element into a non disabled element

@jquense
Copy link
Contributor Author

jquense commented Jun 29, 2015

So here is an initial attempt at a fix but I can't figure out how to properly use EventPropagators here. Is there a way I am missing to listen for child events (i.e mouseout/over) but also return an event that doesn't bubble itself?

the below only listens for mouseout/over on the element that has the callback attached :/

var EventConstants = require("./EventConstants");
var EventPropagators = require("./EventPropagators");
var SyntheticMouseEvent = require("./SyntheticMouseEvent");
var containsNode = require("./containsNode");

var ReactMount = require("./ReactMount");
var keyOf = require("./keyOf");

var topLevelTypes = EventConstants.topLevelTypes;
var getFirstReactDOM = ReactMount.getFirstReactDOM;

var eventTypes = {
  mouseEnter: {

    registrationName: keyOf({ onMouseEnter: null }),
    dependencies: [topLevelTypes.topMouseOut, topLevelTypes.topMouseOver]
  },
  mouseLeave: {
    registrationName: keyOf({ onMouseLeave: null }),
    dependencies: [topLevelTypes.topMouseOut, topLevelTypes.topMouseOver]
  }
};

var extractedEvents = [null, null];

var EnterLeaveEventPlugin = {

  eventTypes: eventTypes,

  /**
   * For almost every interaction we care about, there will be both a top-level
   * `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
   * we do not extract duplicate events. However, moving the mouse into the
   * browser from outside will not fire a `mouseout` event. In this case, we use
   * the `mouseover` top-level event.
   *
   * @param {string} topLevelType Record from `EventConstants`.
   * @param {DOMEventTarget} topLevelTarget The listening component root node.
   * @param {string} topLevelTargetID ID of `topLevelTarget`.
   * @param {object} nativeEvent Native browser event.
   * @return {*} An accumulation of synthetic events.
   * @see {EventPluginHub.extractEvents}
   */
  extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {

    if (topLevelType !== topLevelTypes.topMouseOut && topLevelType !== topLevelTypes.topMouseOver) {
      // Must not be a mouse in or mouse out - ignoring.
      return null;
    }

    var win;
    if (topLevelTarget.window === topLevelTarget) {
      // `topLevelTarget` is probably a window object.
      win = topLevelTarget;
    } else {
      // TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
      var doc = topLevelTarget.ownerDocument;
      if (doc) {
        win = doc.defaultView || doc.parentWindow;
      } else {
        win = window;
      }
    }

    var eventType;

    var target = getFirstReactDOM(nativeEvent.target) || win;
    var related = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement);

    //console.log('hii!!')
    if (!related || related !== target && !containsNode(target, related)) {

      related = related || win;

      if (topLevelType === topLevelTypes.topMouseOut) {
        eventType = 'mouseLeave';
      } else {
        eventType = 'mouseEnter';
      }

      var event = SyntheticMouseEvent.getPooled(eventTypes[eventType], topLevelTargetID, nativeEvent);

      event.type = eventType.toLowerCase();
      //event.target = target;
      event.relatedTarget = related;

      // this isn't right!~!!
      EventPropagators.accumulateDirectDispatches(event);

      return event;
    }

    return null;
  }

};

module.exports = EnterLeaveEventPlugin;

@jimfb
Copy link
Contributor

jimfb commented Jul 6, 2015

@syranide @sebmarkbage

@rogchap
Copy link

rogchap commented Aug 18, 2015

I am pretty sure the correct behavior here is that neither event should fire if its target is disabled.

An example of when you would want mouseleave to fire on a disabled element is if you disabled the button onclick.

Think of a payment button: mouseenter is called changing the hover state {hover:true} then onclick sets state to disabled {disabled:true}, meanwhile, user moves mouse away from button and the button state is changed back to enabled {disabled:false}. The button now is in a state that is incorrect as it currently has the state {hover:true}.

@jquense
Copy link
Contributor Author

jquense commented Dec 17, 2015

Ping @jimfb @sebmarkbage @syranide

Got bit by this again, I'd be happy to PR something, but the current mouseleave/enter code seems really deeply integrated (has its own event propagator?). My current stumbling block is just the below:

Is there a way I am missing to listen for child events (i.e mouseout/over) but also return an event that doesn't bubble itself?

The problem is that I need to emit an event that does not fire when the dependent DOM events (out/leave) fire. tests seem using other examples seem either to emit for every child event, or not respond to bubbled child events at all.

@jimfb
Copy link
Contributor

jimfb commented Dec 17, 2015

adding @spicyj.

@syranide and @spicyj: either of you know of a good example where we normalize in this way? Can either of you provide some tips here?

@sophiebits
Copy link
Collaborator

No, I'm not sure. I would be inclined to think that both mouseenter and mouseleave should fire. It's a little weird to me that click doesn't but I can kind of justify that one in my mind.

@jquense
Copy link
Contributor Author

jquense commented Dec 18, 2015

@spicyj I tend to agree with you, the problem though is that both mouseout and mouseover do not fire on disabled elements. I am not sure why...all spec stuff I've seen suggests that just click events shouldn't, but browsers seem to go with "all mouse events". In that context it might more consistent to also not allow mouse enter/leave as well. But to be honest its all a moot discussion unless there is an implementation that doesn't rely on either mouseout or mouseover, which may be possible but not common it seems.

@andykog
Copy link

andykog commented Dec 29, 2015

There is also a problem for elements, containing disabled element.
onMouseEnter works, onMouseLeave doesn't.

<div onMouseEnter={e => console.log("ok")}
     onMouseLeave={e => alert("doesn't work")}
>
    <button disabled={true} style={{ width: "100%" }}>Test</button>
</div>

Native mouseleave event works as expected in the same situation.

@chicoxyzzy
Copy link
Contributor

A have same issue. I expect to get onMouseLeave event on disabled button but in doesn't work

@chicoxyzzy
Copy link
Contributor

More on this: it works proper in Firefox but doesn't work in Chrome and Safari

@attilaaronnagy
Copy link

After react 15, onClick on disabled elements does not fire. That makes me a ton of trouble. Should I open an other issue?

@jquense
Copy link
Contributor Author

jquense commented May 23, 2016

@attilaaronnagy fairly certain it's not a React issue, but a browser one.

@attilaaronnagy
Copy link

attilaaronnagy commented May 23, 2016

I'm using the latest chrome on the latest mac and with react 0.14.4 this worked perfectly... So I don't think so. Just create an <input type="text" onClick={this.whatever} disabled />, and the onClick won't fire.

@sophiebits
Copy link
Collaborator

@attilaaronnagy I believe this is expected and matches the HTML/DOM spec. @gaearon Did we miss this in the changelog?

@attilaaronnagy
Copy link

@spicyj and if I need an input, select etc. that is disabled, but has an onClick event what can I do now? (we made a graphical html editor, and I would like to select the components with onClick, but when the edit mode active I would like to disable the component so I can't 'use' it can just 'select' it)

@attilaaronnagy
Copy link

@spicyj even if I try this:

this.refs.textbox.addEventListener( 'click', clickEvent, true ); <input ref="textbox" />

it's still not working. I don't know what kind of magic you have guys cooked up, but when it's disabled, you are blocking every possible thing no matter whatever I do. I'm thinking about puting a global click event listener on the body and backward calculating which one of the components has been clicked from the coordinates, but that is absurd... Common...

@sophiebits
Copy link
Collaborator

@attilaaronnagy Browsers don't support click events on inputs. We don't do anything that would affect how addEventListener works.

@attilaaronnagy
Copy link

@spicyj if the element is not disabled: this code works and the "normal" onClick is also working on an input.

I just tried with the same browser in a jsbin, with the older react (0.13) and it's working with the disabled input also. So if the only difference is the react version number... but whatever I gave up.

@sophiebits
Copy link
Collaborator

@attilaaronnagy This jsbin using React 0.13.3 doesn't work for me:

http://react.jsbin.com/qesulefepu/edit?html,js,output

Let me know if you're seeing otherwise.

@attilaaronnagy
Copy link

@attilaaronnagy
Copy link

@spicyj https://jsfiddle.net/qfLzkz5x/

works for me...

@sophiebits
Copy link
Collaborator

https://jsfiddle.net/qfLzkz5x/ doesn't have a disabled input; if you disable it then you see the behavior I posted. That is the browser's doing, not React's.

If you want to capture the onClick event on a disabled input, you have to put a wrapper node around it (or listen to it at the top level, if you prefer). This matches the standard DOM behavior. I personally think this behavior is surprising and not desirable, but we find it valuable to match the DOM spec for this so we're planning to leave it this way.

@CoryDanielson
Copy link

CoryDanielson commented Jul 27, 2016

Any updates on this onMouseLeave issue? I'm having the same issue with almost identical code to what @andykog posted.

https://jsfiddle.net/qfLzkz5x/1/

As a workaround, I've updated my component to watch for the native mouseleave event on the parent of the disabled element, which seems to work, but fires more than expected. My workaround is using code similar to this:

https://jsfiddle.net/qfLzkz5x/8/

Edit: Same code with the events bound directly to the disabled button. The native mouseleave event does not work in this case, either. (on Chrome and FF)

https://jsfiddle.net/qfLzkz5x/6/

@andykog
Copy link

andykog commented Dec 14, 2016

There is also a css workaroud:

button[disabled] { pointer-events: none; }

Updated @CoryDanielson's fiddle: https://jsfiddle.net/Sl1v3r/sLsut3cy/

@A11oW
Copy link

A11oW commented Feb 17, 2017

More experiments http://www.webpackbin.com/VJeejto1Kf

@SebVory
Copy link

SebVory commented Feb 25, 2020

Guys, any progress please? Just came over it in real situation when I have text explaining functionality of some actions user can make. However In one case (in compare mode) the download mode of CSV is disabled. Then OnMouseEnter shows the text correctly but the text stays there even user scroll away because the OnMouseLeave doesn't work. Very inconsistent for sure a bug without a doubt. Hoping for quick fix! Thanks.

button[disabled] { pointer-events: none; } will disable even OnMouseEnter event. But I want to allow OnMouseLeave.

<button
onMouseEnter={() => setHoveredText(dataToCompare ? advices.csvDisabled : advices.csv)} // works
onMouseLeave={() => setHoveredText(null)} // doesn't work !!!
onClick={() => exportTableToCSV('TIL-table-data.csv')}
disabled={dataToCompare}
> ICON </button>

Snímek obrazovky 2020-02-25 v 9 09 11
Snímek obrazovky 2020-02-25 v 9 09 29

@lyleunderwood
Copy link

As this seems to be caused by an underlying chrome bug, I'm not sure if a solution is forthcoming.

@gaearon
Copy link
Collaborator

gaearon commented Feb 26, 2020

Should be fixed in 16.13.
https://reactjs.org/blog/2020/03/02/react-v16.13.0.html

If not please create a new issue with a reproducing example.

@gaearon gaearon closed this as completed Feb 26, 2020
@lyleunderwood
Copy link

lyleunderwood commented Feb 26, 2020

To be clear, the change in 16.13 seems to be that mouseenter will no longer fire for disabled inputs.

@RithikaChowta
Copy link

onMouseOut still works on disabled elements though, so it can be used as an alternative to onMouseLeave

@jquense
Copy link
Contributor Author

jquense commented Jul 20, 2020

This is still broken: #19419 opened a new issue for it

denkristoffer added a commit to contentful/forma-36 that referenced this issue Jan 26, 2021
denkristoffer added a commit to contentful/forma-36 that referenced this issue Jan 27, 2021
denkristoffer added a commit to contentful/forma-36 that referenced this issue Jan 27, 2021
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