Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

371 строка
11 KiB

  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) :
  3. typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) :
  4. (factory((global.onClickOutside = {}),global.React,global.ReactDOM));
  5. }(this, (function (exports,react,reactDom) { 'use strict';
  6. function _inheritsLoose(subClass, superClass) {
  7. subClass.prototype = Object.create(superClass.prototype);
  8. subClass.prototype.constructor = subClass;
  9. subClass.__proto__ = superClass;
  10. }
  11. function _objectWithoutProperties(source, excluded) {
  12. if (source == null) return {};
  13. var target = {};
  14. var sourceKeys = Object.keys(source);
  15. var key, i;
  16. for (i = 0; i < sourceKeys.length; i++) {
  17. key = sourceKeys[i];
  18. if (excluded.indexOf(key) >= 0) continue;
  19. target[key] = source[key];
  20. }
  21. if (Object.getOwnPropertySymbols) {
  22. var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
  23. for (i = 0; i < sourceSymbolKeys.length; i++) {
  24. key = sourceSymbolKeys[i];
  25. if (excluded.indexOf(key) >= 0) continue;
  26. if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
  27. target[key] = source[key];
  28. }
  29. }
  30. return target;
  31. }
  32. /**
  33. * Check whether some DOM node is our Component's node.
  34. */
  35. function isNodeFound(current, componentNode, ignoreClass) {
  36. if (current === componentNode) {
  37. return true;
  38. } // SVG <use/> elements do not technically reside in the rendered DOM, so
  39. // they do not have classList directly, but they offer a link to their
  40. // corresponding element, which can have classList. This extra check is for
  41. // that case.
  42. // See: http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGUseElement
  43. // Discussion: https://github.com/Pomax/react-onclickoutside/pull/17
  44. if (current.correspondingElement) {
  45. return current.correspondingElement.classList.contains(ignoreClass);
  46. }
  47. return current.classList.contains(ignoreClass);
  48. }
  49. /**
  50. * Try to find our node in a hierarchy of nodes, returning the document
  51. * node as highest node if our node is not found in the path up.
  52. */
  53. function findHighest(current, componentNode, ignoreClass) {
  54. if (current === componentNode) {
  55. return true;
  56. } // If source=local then this event came from 'somewhere'
  57. // inside and should be ignored. We could handle this with
  58. // a layered approach, too, but that requires going back to
  59. // thinking in terms of Dom node nesting, running counter
  60. // to React's 'you shouldn't care about the DOM' philosophy.
  61. while (current.parentNode) {
  62. if (isNodeFound(current, componentNode, ignoreClass)) {
  63. return true;
  64. }
  65. current = current.parentNode;
  66. }
  67. return current;
  68. }
  69. /**
  70. * Check if the browser scrollbar was clicked
  71. */
  72. function clickedScrollbar(evt) {
  73. return document.documentElement.clientWidth <= evt.clientX || document.documentElement.clientHeight <= evt.clientY;
  74. }
  75. // ideally will get replaced with external dep
  76. // when rafrex/detect-passive-events#4 and rafrex/detect-passive-events#5 get merged in
  77. var testPassiveEventSupport = function testPassiveEventSupport() {
  78. if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {
  79. return;
  80. }
  81. var passive = false;
  82. var options = Object.defineProperty({}, 'passive', {
  83. get: function get() {
  84. passive = true;
  85. }
  86. });
  87. var noop = function noop() {};
  88. window.addEventListener('testPassiveEventSupport', noop, options);
  89. window.removeEventListener('testPassiveEventSupport', noop, options);
  90. return passive;
  91. };
  92. function autoInc(seed) {
  93. if (seed === void 0) {
  94. seed = 0;
  95. }
  96. return function () {
  97. return ++seed;
  98. };
  99. }
  100. var uid = autoInc();
  101. var passiveEventSupport;
  102. var handlersMap = {};
  103. var enabledInstances = {};
  104. var touchEvents = ['touchstart', 'touchmove'];
  105. var IGNORE_CLASS_NAME = 'ignore-react-onclickoutside';
  106. /**
  107. * Options for addEventHandler and removeEventHandler
  108. */
  109. function getEventHandlerOptions(instance, eventName) {
  110. var handlerOptions = null;
  111. var isTouchEvent = touchEvents.indexOf(eventName) !== -1;
  112. if (isTouchEvent && passiveEventSupport) {
  113. handlerOptions = {
  114. passive: !instance.props.preventDefault
  115. };
  116. }
  117. return handlerOptions;
  118. }
  119. /**
  120. * This function generates the HOC function that you'll use
  121. * in order to impart onOutsideClick listening to an
  122. * arbitrary component. It gets called at the end of the
  123. * bootstrapping code to yield an instance of the
  124. * onClickOutsideHOC function defined inside setupHOC().
  125. */
  126. function onClickOutsideHOC(WrappedComponent, config) {
  127. var _class, _temp;
  128. var componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
  129. return _temp = _class =
  130. /*#__PURE__*/
  131. function (_Component) {
  132. _inheritsLoose(onClickOutside, _Component);
  133. function onClickOutside(props) {
  134. var _this;
  135. _this = _Component.call(this, props) || this;
  136. _this.__outsideClickHandler = function (event) {
  137. if (typeof _this.__clickOutsideHandlerProp === 'function') {
  138. _this.__clickOutsideHandlerProp(event);
  139. return;
  140. }
  141. var instance = _this.getInstance();
  142. if (typeof instance.props.handleClickOutside === 'function') {
  143. instance.props.handleClickOutside(event);
  144. return;
  145. }
  146. if (typeof instance.handleClickOutside === 'function') {
  147. instance.handleClickOutside(event);
  148. return;
  149. }
  150. throw new Error("WrappedComponent: " + componentName + " lacks a handleClickOutside(event) function for processing outside click events.");
  151. };
  152. _this.__getComponentNode = function () {
  153. var instance = _this.getInstance();
  154. if (config && typeof config.setClickOutsideRef === 'function') {
  155. return config.setClickOutsideRef()(instance);
  156. }
  157. if (typeof instance.setClickOutsideRef === 'function') {
  158. return instance.setClickOutsideRef();
  159. }
  160. return reactDom.findDOMNode(instance);
  161. };
  162. _this.enableOnClickOutside = function () {
  163. if (typeof document === 'undefined' || enabledInstances[_this._uid]) {
  164. return;
  165. }
  166. if (typeof passiveEventSupport === 'undefined') {
  167. passiveEventSupport = testPassiveEventSupport();
  168. }
  169. enabledInstances[_this._uid] = true;
  170. var events = _this.props.eventTypes;
  171. if (!events.forEach) {
  172. events = [events];
  173. }
  174. handlersMap[_this._uid] = function (event) {
  175. if (_this.componentNode === null) return;
  176. if (_this.props.preventDefault) {
  177. event.preventDefault();
  178. }
  179. if (_this.props.stopPropagation) {
  180. event.stopPropagation();
  181. }
  182. if (_this.props.excludeScrollbar && clickedScrollbar(event)) return;
  183. var current = event.target;
  184. if (findHighest(current, _this.componentNode, _this.props.outsideClickIgnoreClass) !== document) {
  185. return;
  186. }
  187. _this.__outsideClickHandler(event);
  188. };
  189. events.forEach(function (eventName) {
  190. document.addEventListener(eventName, handlersMap[_this._uid], getEventHandlerOptions(_this, eventName));
  191. });
  192. };
  193. _this.disableOnClickOutside = function () {
  194. delete enabledInstances[_this._uid];
  195. var fn = handlersMap[_this._uid];
  196. if (fn && typeof document !== 'undefined') {
  197. var events = _this.props.eventTypes;
  198. if (!events.forEach) {
  199. events = [events];
  200. }
  201. events.forEach(function (eventName) {
  202. return document.removeEventListener(eventName, fn, getEventHandlerOptions(_this, eventName));
  203. });
  204. delete handlersMap[_this._uid];
  205. }
  206. };
  207. _this.getRef = function (ref) {
  208. return _this.instanceRef = ref;
  209. };
  210. _this._uid = uid();
  211. return _this;
  212. }
  213. /**
  214. * Access the WrappedComponent's instance.
  215. */
  216. var _proto = onClickOutside.prototype;
  217. _proto.getInstance = function getInstance() {
  218. if (!WrappedComponent.prototype.isReactComponent) {
  219. return this;
  220. }
  221. var ref = this.instanceRef;
  222. return ref.getInstance ? ref.getInstance() : ref;
  223. };
  224. /**
  225. * Add click listeners to the current document,
  226. * linked to this component's state.
  227. */
  228. _proto.componentDidMount = function componentDidMount() {
  229. // If we are in an environment without a DOM such
  230. // as shallow rendering or snapshots then we exit
  231. // early to prevent any unhandled errors being thrown.
  232. if (typeof document === 'undefined' || !document.createElement) {
  233. return;
  234. }
  235. var instance = this.getInstance();
  236. if (config && typeof config.handleClickOutside === 'function') {
  237. this.__clickOutsideHandlerProp = config.handleClickOutside(instance);
  238. if (typeof this.__clickOutsideHandlerProp !== 'function') {
  239. throw new Error("WrappedComponent: " + componentName + " lacks a function for processing outside click events specified by the handleClickOutside config option.");
  240. }
  241. }
  242. this.componentNode = this.__getComponentNode(); // return early so we dont initiate onClickOutside
  243. if (this.props.disableOnClickOutside) return;
  244. this.enableOnClickOutside();
  245. };
  246. _proto.componentDidUpdate = function componentDidUpdate() {
  247. this.componentNode = this.__getComponentNode();
  248. };
  249. /**
  250. * Remove all document's event listeners for this component
  251. */
  252. _proto.componentWillUnmount = function componentWillUnmount() {
  253. this.disableOnClickOutside();
  254. };
  255. /**
  256. * Can be called to explicitly enable event listening
  257. * for clicks and touches outside of this element.
  258. */
  259. /**
  260. * Pass-through render
  261. */
  262. _proto.render = function render() {
  263. // eslint-disable-next-line no-unused-vars
  264. var _props = this.props,
  265. excludeScrollbar = _props.excludeScrollbar,
  266. props = _objectWithoutProperties(_props, ["excludeScrollbar"]);
  267. if (WrappedComponent.prototype.isReactComponent) {
  268. props.ref = this.getRef;
  269. } else {
  270. props.wrappedRef = this.getRef;
  271. }
  272. props.disableOnClickOutside = this.disableOnClickOutside;
  273. props.enableOnClickOutside = this.enableOnClickOutside;
  274. return react.createElement(WrappedComponent, props);
  275. };
  276. return onClickOutside;
  277. }(react.Component), _class.displayName = "OnClickOutside(" + componentName + ")", _class.defaultProps = {
  278. eventTypes: ['mousedown', 'touchstart'],
  279. excludeScrollbar: config && config.excludeScrollbar || false,
  280. outsideClickIgnoreClass: IGNORE_CLASS_NAME,
  281. preventDefault: false,
  282. stopPropagation: false
  283. }, _class.getClass = function () {
  284. return WrappedComponent.getClass ? WrappedComponent.getClass() : WrappedComponent;
  285. }, _temp;
  286. }
  287. exports.IGNORE_CLASS_NAME = IGNORE_CLASS_NAME;
  288. exports['default'] = onClickOutsideHOC;
  289. Object.defineProperty(exports, '__esModule', { value: true });
  290. })));