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

excludeScrollbar does not exclude scrollbar clicks for non-top-level elements #167

Closed
jameslaneconkling opened this issue Apr 17, 2017 · 6 comments

Comments

@jameslaneconkling
Copy link

This likely isn't a bug, as the docs don't say it's expected behavior. But perhaps could be an enhancement.

It looks like the HOC scrollbar click check only works if the scrollbar is on an element with the same dimensions as the page. This means it wouldn't work if the scrollbar is on an element somewhere further down the DOM tree, e.g. below:

screenshot 2017-04-17 12 21 46

Should scrolling the above John Doe element trigger the click event when excludeScrollbar is set to true? Is it even possible to determine if the click is on a scrollbar?

Would be willing to put together a PR if it's desired functionality.

@Pomax
Copy link
Owner

Pomax commented Apr 17, 2017

The scrollbar fix was actually purely for the document scrollbars, it does not check any elements as such. If you want to extend it so that it covers scrollbars on "anything", I'll be happy to review and merge it in when it does what it should do while also passing tests!

@jameslaneconkling
Copy link
Author

OK, I'll take a swing at it. A quick google search returns approaches similar/identical to what's currently implemented. But I did notice that bootstrap's typeahead component (which closes on outside clicks) didn't close on non-document scroll bar clicks, so I might dig in there for other possible approaches.

@jameslaneconkling
Copy link
Author

Hmmm, interesting.

I was looking at twitter bootstrap's typeahead component to figure out how they were able to implement the functionality (close on click outside the component, but not on scrollbars), and realized they didn't capture onClick events on external elements at all, but rather monitored the onBlur event of the component itself. An onBlur event is triggered whenever focus is lost on the element, and you can use the blur event's relatedTarget property to determine whether the new focused element is a child of the decorated component, or whether it is external.

e.g.

const closeOnOutsideClick = () => (BaseComponent) => {
  return (
    class CloseOnOutsideClick extends React.Component {
      constructor(props) {
        super(props);
        this.onBlur = this.onBlur.bind(this);
      }

      componentDidMount() {
        this.input.focus();
      }

      onBlur({ currentTarget, relatedTarget }) {
        // currentTarget is HOC Wrapper
        // relatedTarget is whatever element is gaining focus, if any
        // if element gaining focus is w/i Wrapper, refocus on Wrapper
        // otherwise, bubble the onClose event

        if (!relatedTarget || !currentTarget.contains(relatedTarget)) {
          this.props.onClose();
        } else {
          this.input.focus();
        }
      }
      
      render() {
        return (
          <span
            ref={(input) => { this.input = input; }}
            tabIndex="0"
            onBlur={this.onBlur}
          >
            <BaseComponent {...this.props} />
          </span>
        );
      }
    }
  );
};

This effectively prevents you from having input elements (or anything that requires a focus state) w/i the decorated component. But it solves my problem of closing the decorated component when clicking on a non-top-level scrollbar w/o having to estimate scrollbar width (which I tried, without much success). Also note, might not work in IE <= 11.

In any case, probably not applicable to your use case (?). But could be a useful alternative strategy.

@Pomax
Copy link
Owner

Pomax commented Apr 19, 2017

Note that that universal onblur and onfocus only works when you give elements a tabindex, which is an incredibly web-breaking idea if you want to offer a universal library, because it ruins the page experience for people with disabilities who have to tab through your UI to get to the parts they need. Basically this solution abuses a property that's intended for accessibility, by using it in an accessibility-breaking way. Great if that's not a thing you want to care about, not so great if you do =)

As we don't know where this HOC ends up being used, baking in a worse experience for less than capable users is probably a bad idea. Let's stick with monitoring click.

@jameslaneconkling
Copy link
Author

because it ruins the page experience for people with disabilities who have to tab through your UI to get to the parts they need.

True in your general case, maybe. In my case (a typeahead autocomplete), being able to tab into/out of the component is the intent, rather than a hack. But yeah, this wouldn't apply to the broader "onClickOutside" case. (I suppose I should rename the above HOC closeOnFocusOutside())

@Andarist
Copy link
Collaborator

Closing due to inactivity. If your issue is still unresolved, please comment back and I will reopen this.

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

No branches or pull requests

3 participants