You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
356 lines
11 KiB
356 lines
11 KiB
(function(g,f){typeof exports==='object'&&typeof module!=='undefined'?f(exports,require('react'),require('react-dom')):typeof define==='function'&&define.amd?define(['exports','react','react-dom'],f):(g=typeof globalThis!=='undefined'?globalThis:g||self,f(g.onClickOutside={},g.React,g.ReactDOM));}(this,(function(exports, react, reactDom){'use strict';function _inheritsLoose(subClass, superClass) {
|
|
subClass.prototype = Object.create(superClass.prototype);
|
|
subClass.prototype.constructor = subClass;
|
|
|
|
_setPrototypeOf(subClass, superClass);
|
|
}
|
|
|
|
function _setPrototypeOf(o, p) {
|
|
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
|
o.__proto__ = p;
|
|
return o;
|
|
};
|
|
|
|
return _setPrototypeOf(o, p);
|
|
}
|
|
|
|
function _objectWithoutPropertiesLoose(source, excluded) {
|
|
if (source == null) return {};
|
|
var target = {};
|
|
var sourceKeys = Object.keys(source);
|
|
var key, i;
|
|
|
|
for (i = 0; i < sourceKeys.length; i++) {
|
|
key = sourceKeys[i];
|
|
if (excluded.indexOf(key) >= 0) continue;
|
|
target[key] = source[key];
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
function _assertThisInitialized(self) {
|
|
if (self === void 0) {
|
|
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
}
|
|
|
|
return self;
|
|
}/**
|
|
* Check whether some DOM node is our Component's node.
|
|
*/
|
|
function isNodeFound(current, componentNode, ignoreClass) {
|
|
if (current === componentNode) {
|
|
return true;
|
|
} // SVG <use/> elements do not technically reside in the rendered DOM, so
|
|
// they do not have classList directly, but they offer a link to their
|
|
// corresponding element, which can have classList. This extra check is for
|
|
// that case.
|
|
// See: http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGUseElement
|
|
// Discussion: https://github.com/Pomax/react-onclickoutside/pull/17
|
|
|
|
|
|
if (current.correspondingElement) {
|
|
return current.correspondingElement.classList.contains(ignoreClass);
|
|
}
|
|
|
|
return current.classList.contains(ignoreClass);
|
|
}
|
|
/**
|
|
* Try to find our node in a hierarchy of nodes, returning the document
|
|
* node as highest node if our node is not found in the path up.
|
|
*/
|
|
|
|
function findHighest(current, componentNode, ignoreClass) {
|
|
if (current === componentNode) {
|
|
return true;
|
|
} // If source=local then this event came from 'somewhere'
|
|
// inside and should be ignored. We could handle this with
|
|
// a layered approach, too, but that requires going back to
|
|
// thinking in terms of Dom node nesting, running counter
|
|
// to React's 'you shouldn't care about the DOM' philosophy.
|
|
// Also cover shadowRoot node by checking current.host
|
|
|
|
|
|
while (current.parentNode || current.host) {
|
|
// Only check normal node without shadowRoot
|
|
if (current.parentNode && isNodeFound(current, componentNode, ignoreClass)) {
|
|
return true;
|
|
}
|
|
|
|
current = current.parentNode || current.host;
|
|
}
|
|
|
|
return current;
|
|
}
|
|
/**
|
|
* Check if the browser scrollbar was clicked
|
|
*/
|
|
|
|
function clickedScrollbar(evt) {
|
|
return document.documentElement.clientWidth <= evt.clientX || document.documentElement.clientHeight <= evt.clientY;
|
|
}// ideally will get replaced with external dep
|
|
// when rafrex/detect-passive-events#4 and rafrex/detect-passive-events#5 get merged in
|
|
var testPassiveEventSupport = function testPassiveEventSupport() {
|
|
if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {
|
|
return;
|
|
}
|
|
|
|
var passive = false;
|
|
var options = Object.defineProperty({}, 'passive', {
|
|
get: function get() {
|
|
passive = true;
|
|
}
|
|
});
|
|
|
|
var noop = function noop() {};
|
|
|
|
window.addEventListener('testPassiveEventSupport', noop, options);
|
|
window.removeEventListener('testPassiveEventSupport', noop, options);
|
|
return passive;
|
|
};function autoInc(seed) {
|
|
if (seed === void 0) {
|
|
seed = 0;
|
|
}
|
|
|
|
return function () {
|
|
return ++seed;
|
|
};
|
|
}
|
|
|
|
var uid = autoInc();var passiveEventSupport;
|
|
var handlersMap = {};
|
|
var enabledInstances = {};
|
|
var touchEvents = ['touchstart', 'touchmove'];
|
|
var IGNORE_CLASS_NAME = 'ignore-react-onclickoutside';
|
|
/**
|
|
* Options for addEventHandler and removeEventHandler
|
|
*/
|
|
|
|
function getEventHandlerOptions(instance, eventName) {
|
|
var handlerOptions = null;
|
|
var isTouchEvent = touchEvents.indexOf(eventName) !== -1;
|
|
|
|
if (isTouchEvent && passiveEventSupport) {
|
|
handlerOptions = {
|
|
passive: !instance.props.preventDefault
|
|
};
|
|
}
|
|
|
|
return handlerOptions;
|
|
}
|
|
/**
|
|
* This function generates the HOC function that you'll use
|
|
* in order to impart onOutsideClick listening to an
|
|
* arbitrary component. It gets called at the end of the
|
|
* bootstrapping code to yield an instance of the
|
|
* onClickOutsideHOC function defined inside setupHOC().
|
|
*/
|
|
|
|
|
|
function onClickOutsideHOC(WrappedComponent, config) {
|
|
var _class, _temp;
|
|
|
|
var componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
|
return _temp = _class = /*#__PURE__*/function (_Component) {
|
|
_inheritsLoose(onClickOutside, _Component);
|
|
|
|
function onClickOutside(props) {
|
|
var _this;
|
|
|
|
_this = _Component.call(this, props) || this;
|
|
|
|
_this.__outsideClickHandler = function (event) {
|
|
if (typeof _this.__clickOutsideHandlerProp === 'function') {
|
|
_this.__clickOutsideHandlerProp(event);
|
|
|
|
return;
|
|
}
|
|
|
|
var instance = _this.getInstance();
|
|
|
|
if (typeof instance.props.handleClickOutside === 'function') {
|
|
instance.props.handleClickOutside(event);
|
|
return;
|
|
}
|
|
|
|
if (typeof instance.handleClickOutside === 'function') {
|
|
instance.handleClickOutside(event);
|
|
return;
|
|
}
|
|
|
|
throw new Error("WrappedComponent: " + componentName + " lacks a handleClickOutside(event) function for processing outside click events.");
|
|
};
|
|
|
|
_this.__getComponentNode = function () {
|
|
var instance = _this.getInstance();
|
|
|
|
if (config && typeof config.setClickOutsideRef === 'function') {
|
|
return config.setClickOutsideRef()(instance);
|
|
}
|
|
|
|
if (typeof instance.setClickOutsideRef === 'function') {
|
|
return instance.setClickOutsideRef();
|
|
}
|
|
|
|
return reactDom.findDOMNode(instance);
|
|
};
|
|
|
|
_this.enableOnClickOutside = function () {
|
|
if (typeof document === 'undefined' || enabledInstances[_this._uid]) {
|
|
return;
|
|
}
|
|
|
|
if (typeof passiveEventSupport === 'undefined') {
|
|
passiveEventSupport = testPassiveEventSupport();
|
|
}
|
|
|
|
enabledInstances[_this._uid] = true;
|
|
var events = _this.props.eventTypes;
|
|
|
|
if (!events.forEach) {
|
|
events = [events];
|
|
}
|
|
|
|
handlersMap[_this._uid] = function (event) {
|
|
if (_this.componentNode === null) return;
|
|
|
|
if (_this.props.preventDefault) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
if (_this.props.stopPropagation) {
|
|
event.stopPropagation();
|
|
}
|
|
|
|
if (_this.props.excludeScrollbar && clickedScrollbar(event)) return;
|
|
var current = event.composed && event.composedPath && event.composedPath().shift() || event.target;
|
|
|
|
if (findHighest(current, _this.componentNode, _this.props.outsideClickIgnoreClass) !== document) {
|
|
return;
|
|
}
|
|
|
|
_this.__outsideClickHandler(event);
|
|
};
|
|
|
|
events.forEach(function (eventName) {
|
|
document.addEventListener(eventName, handlersMap[_this._uid], getEventHandlerOptions(_assertThisInitialized(_this), eventName));
|
|
});
|
|
};
|
|
|
|
_this.disableOnClickOutside = function () {
|
|
delete enabledInstances[_this._uid];
|
|
var fn = handlersMap[_this._uid];
|
|
|
|
if (fn && typeof document !== 'undefined') {
|
|
var events = _this.props.eventTypes;
|
|
|
|
if (!events.forEach) {
|
|
events = [events];
|
|
}
|
|
|
|
events.forEach(function (eventName) {
|
|
return document.removeEventListener(eventName, fn, getEventHandlerOptions(_assertThisInitialized(_this), eventName));
|
|
});
|
|
delete handlersMap[_this._uid];
|
|
}
|
|
};
|
|
|
|
_this.getRef = function (ref) {
|
|
return _this.instanceRef = ref;
|
|
};
|
|
|
|
_this._uid = uid();
|
|
return _this;
|
|
}
|
|
/**
|
|
* Access the WrappedComponent's instance.
|
|
*/
|
|
|
|
|
|
var _proto = onClickOutside.prototype;
|
|
|
|
_proto.getInstance = function getInstance() {
|
|
if (WrappedComponent.prototype && !WrappedComponent.prototype.isReactComponent) {
|
|
return this;
|
|
}
|
|
|
|
var ref = this.instanceRef;
|
|
return ref.getInstance ? ref.getInstance() : ref;
|
|
};
|
|
|
|
/**
|
|
* Add click listeners to the current document,
|
|
* linked to this component's state.
|
|
*/
|
|
_proto.componentDidMount = function componentDidMount() {
|
|
// If we are in an environment without a DOM such
|
|
// as shallow rendering or snapshots then we exit
|
|
// early to prevent any unhandled errors being thrown.
|
|
if (typeof document === 'undefined' || !document.createElement) {
|
|
return;
|
|
}
|
|
|
|
var instance = this.getInstance();
|
|
|
|
if (config && typeof config.handleClickOutside === 'function') {
|
|
this.__clickOutsideHandlerProp = config.handleClickOutside(instance);
|
|
|
|
if (typeof this.__clickOutsideHandlerProp !== 'function') {
|
|
throw new Error("WrappedComponent: " + componentName + " lacks a function for processing outside click events specified by the handleClickOutside config option.");
|
|
}
|
|
}
|
|
|
|
this.componentNode = this.__getComponentNode(); // return early so we dont initiate onClickOutside
|
|
|
|
if (this.props.disableOnClickOutside) return;
|
|
this.enableOnClickOutside();
|
|
};
|
|
|
|
_proto.componentDidUpdate = function componentDidUpdate() {
|
|
this.componentNode = this.__getComponentNode();
|
|
}
|
|
/**
|
|
* Remove all document's event listeners for this component
|
|
*/
|
|
;
|
|
|
|
_proto.componentWillUnmount = function componentWillUnmount() {
|
|
this.disableOnClickOutside();
|
|
}
|
|
/**
|
|
* Can be called to explicitly enable event listening
|
|
* for clicks and touches outside of this element.
|
|
*/
|
|
;
|
|
|
|
/**
|
|
* Pass-through render
|
|
*/
|
|
_proto.render = function render() {
|
|
// eslint-disable-next-line no-unused-vars
|
|
var _this$props = this.props;
|
|
_this$props.excludeScrollbar;
|
|
var props = _objectWithoutPropertiesLoose(_this$props, ["excludeScrollbar"]);
|
|
|
|
if (WrappedComponent.prototype && WrappedComponent.prototype.isReactComponent) {
|
|
props.ref = this.getRef;
|
|
} else {
|
|
props.wrappedRef = this.getRef;
|
|
}
|
|
|
|
props.disableOnClickOutside = this.disableOnClickOutside;
|
|
props.enableOnClickOutside = this.enableOnClickOutside;
|
|
return react.createElement(WrappedComponent, props);
|
|
};
|
|
|
|
return onClickOutside;
|
|
}(react.Component), _class.displayName = "OnClickOutside(" + componentName + ")", _class.defaultProps = {
|
|
eventTypes: ['mousedown', 'touchstart'],
|
|
excludeScrollbar: config && config.excludeScrollbar || false,
|
|
outsideClickIgnoreClass: IGNORE_CLASS_NAME,
|
|
preventDefault: false,
|
|
stopPropagation: false
|
|
}, _class.getClass = function () {
|
|
return WrappedComponent.getClass ? WrappedComponent.getClass() : WrappedComponent;
|
|
}, _temp;
|
|
}exports.IGNORE_CLASS_NAME=IGNORE_CLASS_NAME;exports.default=onClickOutsideHOC;Object.defineProperty(exports,'__esModule',{value:true});}))); |