Skip to content

mouseenter fires on disabled inputs whereas mouseleave does not #4251

@jquense

Description

@jquense
Contributor

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

Activity

jquense

jquense commented on Jun 29, 2015

@jquense
ContributorAuthor

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

jimfb commented on Jul 6, 2015

@jimfb
Contributor
rogchap

rogchap commented on Aug 18, 2015

@rogchap

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

jquense commented on Dec 17, 2015

@jquense
ContributorAuthor

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

jimfb commented on Dec 17, 2015

@jimfb
Contributor

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

sophiebits commented on Dec 18, 2015

@sophiebits
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

jquense commented on Dec 18, 2015

@jquense
ContributorAuthor

@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

andykog commented on Dec 29, 2015

@andykog

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

chicoxyzzy commented on Mar 7, 2016

@chicoxyzzy
Contributor

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

chicoxyzzy

chicoxyzzy commented on Mar 7, 2016

@chicoxyzzy
Contributor

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

attilaaronnagy

attilaaronnagy commented on May 23, 2016

@attilaaronnagy

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

64 remaining items

jquense

jquense commented on Jul 20, 2020

@jquense
ContributorAuthor

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

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sophiebits@JasonBerry@rogchap@jquense@lyleunderwood

        Issue actions

          mouseenter fires on disabled inputs whereas mouseleave does not · Issue #4251 · facebook/react