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.

Popover.js 16 KiB

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _extends2 = require('babel-runtime/helpers/extends');
  6. var _extends3 = _interopRequireDefault(_extends2);
  7. var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
  8. var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
  9. var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
  10. var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
  11. var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
  12. var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
  13. var _createClass2 = require('babel-runtime/helpers/createClass');
  14. var _createClass3 = _interopRequireDefault(_createClass2);
  15. var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
  16. var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
  17. var _inherits2 = require('babel-runtime/helpers/inherits');
  18. var _inherits3 = _interopRequireDefault(_inherits2);
  19. var _simpleAssign = require('simple-assign');
  20. var _simpleAssign2 = _interopRequireDefault(_simpleAssign);
  21. var _react = require('react');
  22. var _react2 = _interopRequireDefault(_react);
  23. var _propTypes = require('prop-types');
  24. var _propTypes2 = _interopRequireDefault(_propTypes);
  25. var _reactDom = require('react-dom');
  26. var _reactDom2 = _interopRequireDefault(_reactDom);
  27. var _reactEventListener = require('react-event-listener');
  28. var _reactEventListener2 = _interopRequireDefault(_reactEventListener);
  29. var _RenderToLayer = require('../internal/RenderToLayer');
  30. var _RenderToLayer2 = _interopRequireDefault(_RenderToLayer);
  31. var _propTypes3 = require('../utils/propTypes');
  32. var _propTypes4 = _interopRequireDefault(_propTypes3);
  33. var _Paper = require('../Paper');
  34. var _Paper2 = _interopRequireDefault(_Paper);
  35. var _lodash = require('lodash.throttle');
  36. var _lodash2 = _interopRequireDefault(_lodash);
  37. var _PopoverAnimationDefault = require('./PopoverAnimationDefault');
  38. var _PopoverAnimationDefault2 = _interopRequireDefault(_PopoverAnimationDefault);
  39. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  40. var styles = {
  41. root: {
  42. display: 'none'
  43. }
  44. };
  45. var Popover = function (_Component) {
  46. (0, _inherits3.default)(Popover, _Component);
  47. function Popover(props, context) {
  48. (0, _classCallCheck3.default)(this, Popover);
  49. var _this = (0, _possibleConstructorReturn3.default)(this, (Popover.__proto__ || (0, _getPrototypeOf2.default)(Popover)).call(this, props, context));
  50. _this.timeout = null;
  51. _this.renderLayer = function () {
  52. var _this$props = _this.props,
  53. animated = _this$props.animated,
  54. animation = _this$props.animation,
  55. anchorEl = _this$props.anchorEl,
  56. anchorOrigin = _this$props.anchorOrigin,
  57. autoCloseWhenOffScreen = _this$props.autoCloseWhenOffScreen,
  58. canAutoPosition = _this$props.canAutoPosition,
  59. children = _this$props.children,
  60. onRequestClose = _this$props.onRequestClose,
  61. style = _this$props.style,
  62. targetOrigin = _this$props.targetOrigin,
  63. useLayerForClickAway = _this$props.useLayerForClickAway,
  64. scrollableContainer = _this$props.scrollableContainer,
  65. other = (0, _objectWithoutProperties3.default)(_this$props, ['animated', 'animation', 'anchorEl', 'anchorOrigin', 'autoCloseWhenOffScreen', 'canAutoPosition', 'children', 'onRequestClose', 'style', 'targetOrigin', 'useLayerForClickAway', 'scrollableContainer']);
  66. var styleRoot = style;
  67. if (!animated) {
  68. styleRoot = {
  69. position: 'fixed',
  70. zIndex: _this.context.muiTheme.zIndex.popover
  71. };
  72. if (!_this.state.open) {
  73. return null;
  74. }
  75. return _react2.default.createElement(
  76. _Paper2.default,
  77. (0, _extends3.default)({ style: (0, _simpleAssign2.default)(styleRoot, style) }, other),
  78. children
  79. );
  80. }
  81. var Animation = animation || _PopoverAnimationDefault2.default;
  82. return _react2.default.createElement(
  83. Animation,
  84. (0, _extends3.default)({
  85. targetOrigin: targetOrigin,
  86. style: styleRoot
  87. }, other, {
  88. open: _this.state.open && !_this.state.closing
  89. }),
  90. children
  91. );
  92. };
  93. _this.componentClickAway = function () {
  94. _this.requestClose('clickAway');
  95. };
  96. _this.setPlacement = function (scrolling) {
  97. if (!_this.state.open) {
  98. return;
  99. }
  100. if (!_this.popoverRefs.layer.getLayer()) {
  101. return;
  102. }
  103. var targetEl = _this.popoverRefs.layer.getLayer().children[0];
  104. if (!targetEl) {
  105. return;
  106. }
  107. var _this$props2 = _this.props,
  108. targetOrigin = _this$props2.targetOrigin,
  109. anchorOrigin = _this$props2.anchorOrigin;
  110. var anchorEl = _this.props.anchorEl || _this.anchorEl;
  111. var anchor = _this.getAnchorPosition(anchorEl);
  112. var target = _this.getTargetPosition(targetEl);
  113. var targetPosition = {
  114. top: anchor[anchorOrigin.vertical] - target[targetOrigin.vertical],
  115. left: anchor[anchorOrigin.horizontal] - target[targetOrigin.horizontal]
  116. };
  117. if (scrolling && _this.props.autoCloseWhenOffScreen) {
  118. _this.autoCloseWhenOffScreen(anchor);
  119. }
  120. if (_this.props.canAutoPosition) {
  121. target = _this.getTargetPosition(targetEl); // update as height may have changed
  122. targetPosition = _this.applyAutoPositionIfNeeded(anchor, target, targetOrigin, anchorOrigin, targetPosition);
  123. }
  124. targetEl.style.top = targetPosition.top + 'px';
  125. targetEl.style.left = targetPosition.left + 'px';
  126. targetEl.style.maxHeight = window.innerHeight + 'px';
  127. };
  128. _this.handleResize = (0, _lodash2.default)(_this.setPlacement, 100);
  129. _this.handleScroll = (0, _lodash2.default)(_this.setPlacement.bind(_this, true), 50);
  130. _this.popoverRefs = {};
  131. _this.state = {
  132. open: props.open,
  133. closing: false
  134. };
  135. return _this;
  136. }
  137. (0, _createClass3.default)(Popover, [{
  138. key: 'componentDidMount',
  139. value: function componentDidMount() {
  140. this.placementTimeout = setTimeout(this.setPlacement);
  141. }
  142. }, {
  143. key: 'componentWillReceiveProps',
  144. value: function componentWillReceiveProps(nextProps) {
  145. var _this2 = this;
  146. if (nextProps.open === this.props.open) {
  147. return;
  148. }
  149. if (nextProps.open) {
  150. clearTimeout(this.timeout);
  151. this.timeout = null;
  152. this.anchorEl = nextProps.anchorEl || this.props.anchorEl;
  153. this.setState({
  154. open: true,
  155. closing: false
  156. });
  157. } else {
  158. if (nextProps.animated) {
  159. if (this.timeout !== null) return;
  160. this.setState({ closing: true });
  161. this.timeout = setTimeout(function () {
  162. _this2.setState({
  163. open: false
  164. }, function () {
  165. _this2.timeout = null;
  166. });
  167. }, 500);
  168. } else {
  169. this.setState({
  170. open: false
  171. });
  172. }
  173. }
  174. }
  175. }, {
  176. key: 'componentDidUpdate',
  177. value: function componentDidUpdate() {
  178. clearTimeout(this.placementTimeout);
  179. this.placementTimeout = setTimeout(this.setPlacement);
  180. }
  181. }, {
  182. key: 'componentWillUnmount',
  183. value: function componentWillUnmount() {
  184. this.handleResize.cancel();
  185. this.handleScroll.cancel();
  186. if (this.placementTimeout) {
  187. clearTimeout(this.placementTimeout);
  188. this.placementTimeout = null;
  189. }
  190. if (this.timeout) {
  191. clearTimeout(this.timeout);
  192. this.timeout = null;
  193. }
  194. }
  195. }, {
  196. key: 'requestClose',
  197. value: function requestClose(reason) {
  198. if (this.props.onRequestClose) {
  199. this.props.onRequestClose(reason);
  200. }
  201. }
  202. }, {
  203. key: 'getAnchorPosition',
  204. value: function getAnchorPosition(el) {
  205. if (!el) {
  206. el = _reactDom2.default.findDOMNode(this);
  207. }
  208. var rect = el.getBoundingClientRect();
  209. var a = {
  210. top: rect.top,
  211. left: rect.left,
  212. width: el.offsetWidth,
  213. height: el.offsetHeight
  214. };
  215. a.right = rect.right || a.left + a.width;
  216. a.bottom = rect.bottom || a.top + a.height;
  217. a.middle = a.left + (a.right - a.left) / 2;
  218. a.center = a.top + (a.bottom - a.top) / 2;
  219. return a;
  220. }
  221. }, {
  222. key: 'getTargetPosition',
  223. value: function getTargetPosition(targetEl) {
  224. return {
  225. top: 0,
  226. center: targetEl.offsetHeight / 2,
  227. bottom: targetEl.offsetHeight,
  228. left: 0,
  229. middle: targetEl.offsetWidth / 2,
  230. right: targetEl.offsetWidth
  231. };
  232. }
  233. }, {
  234. key: 'autoCloseWhenOffScreen',
  235. value: function autoCloseWhenOffScreen(anchorPosition) {
  236. if (anchorPosition.top < 0 || anchorPosition.top > window.innerHeight || anchorPosition.left < 0 || anchorPosition.left > window.innerWidth) {
  237. this.requestClose('offScreen');
  238. }
  239. }
  240. }, {
  241. key: 'getOverlapMode',
  242. value: function getOverlapMode(anchor, target, median) {
  243. if ([anchor, target].indexOf(median) >= 0) return 'auto';
  244. if (anchor === target) return 'inclusive';
  245. return 'exclusive';
  246. }
  247. }, {
  248. key: 'getPositions',
  249. value: function getPositions(anchor, target) {
  250. var a = (0, _extends3.default)({}, anchor);
  251. var t = (0, _extends3.default)({}, target);
  252. var positions = {
  253. x: ['left', 'right'].filter(function (p) {
  254. return p !== t.horizontal;
  255. }),
  256. y: ['top', 'bottom'].filter(function (p) {
  257. return p !== t.vertical;
  258. })
  259. };
  260. var overlap = {
  261. x: this.getOverlapMode(a.horizontal, t.horizontal, 'middle'),
  262. y: this.getOverlapMode(a.vertical, t.vertical, 'center')
  263. };
  264. positions.x.splice(overlap.x === 'auto' ? 0 : 1, 0, 'middle');
  265. positions.y.splice(overlap.y === 'auto' ? 0 : 1, 0, 'center');
  266. if (overlap.y !== 'auto') {
  267. a.vertical = a.vertical === 'top' ? 'bottom' : 'top';
  268. if (overlap.y === 'inclusive') {
  269. t.vertical = t.vertical;
  270. }
  271. }
  272. if (overlap.x !== 'auto') {
  273. a.horizontal = a.horizontal === 'left' ? 'right' : 'left';
  274. if (overlap.y === 'inclusive') {
  275. t.horizontal = t.horizontal;
  276. }
  277. }
  278. return {
  279. positions: positions,
  280. anchorPos: a
  281. };
  282. }
  283. }, {
  284. key: 'applyAutoPositionIfNeeded',
  285. value: function applyAutoPositionIfNeeded(anchor, target, targetOrigin, anchorOrigin, targetPosition) {
  286. var _getPositions = this.getPositions(anchorOrigin, targetOrigin),
  287. positions = _getPositions.positions,
  288. anchorPos = _getPositions.anchorPos;
  289. if (targetPosition.top < 0 || targetPosition.top + target.bottom > window.innerHeight) {
  290. var newTop = anchor[anchorPos.vertical] - target[positions.y[0]];
  291. if (newTop + target.bottom <= window.innerHeight) {
  292. targetPosition.top = Math.max(0, newTop);
  293. } else {
  294. newTop = anchor[anchorPos.vertical] - target[positions.y[1]];
  295. if (newTop + target.bottom <= window.innerHeight) {
  296. targetPosition.top = Math.max(0, newTop);
  297. }
  298. }
  299. }
  300. if (targetPosition.left < 0 || targetPosition.left + target.right > window.innerWidth) {
  301. var newLeft = anchor[anchorPos.horizontal] - target[positions.x[0]];
  302. if (newLeft + target.right <= window.innerWidth) {
  303. targetPosition.left = Math.max(0, newLeft);
  304. } else {
  305. newLeft = anchor[anchorPos.horizontal] - target[positions.x[1]];
  306. if (newLeft + target.right <= window.innerWidth) {
  307. targetPosition.left = Math.max(0, newLeft);
  308. }
  309. }
  310. }
  311. return targetPosition;
  312. }
  313. }, {
  314. key: 'render',
  315. value: function render() {
  316. var _this3 = this;
  317. var eventListener = this.state.open ? _react2.default.createElement(_reactEventListener2.default, {
  318. target: this.props.scrollableContainer,
  319. onScroll: this.handleScroll,
  320. onResize: this.handleResize
  321. }) : null;
  322. return _react2.default.createElement(
  323. 'div',
  324. { style: styles.root },
  325. eventListener,
  326. _react2.default.createElement(_RenderToLayer2.default, {
  327. ref: function ref(_ref) {
  328. return _this3.popoverRefs.layer = _ref;
  329. },
  330. open: this.state.open,
  331. componentClickAway: this.componentClickAway,
  332. useLayerForClickAway: this.props.useLayerForClickAway,
  333. render: this.renderLayer
  334. })
  335. );
  336. }
  337. }]);
  338. return Popover;
  339. }(_react.Component);
  340. Popover.defaultProps = {
  341. anchorOrigin: {
  342. vertical: 'bottom',
  343. horizontal: 'left'
  344. },
  345. animated: true,
  346. autoCloseWhenOffScreen: true,
  347. canAutoPosition: true,
  348. onRequestClose: function onRequestClose() {},
  349. open: false,
  350. scrollableContainer: 'window',
  351. style: {
  352. overflowY: 'auto'
  353. },
  354. targetOrigin: {
  355. vertical: 'top',
  356. horizontal: 'left'
  357. },
  358. useLayerForClickAway: true,
  359. zDepth: 1
  360. };
  361. Popover.contextTypes = {
  362. muiTheme: _propTypes2.default.object.isRequired
  363. };
  364. Popover.propTypes = process.env.NODE_ENV !== "production" ? {
  365. /**
  366. * This is the DOM element that will be used to set the position of the
  367. * popover.
  368. */
  369. anchorEl: _propTypes2.default.object,
  370. /**
  371. * This is the point on the anchor where the popover's
  372. * `targetOrigin` will attach to.
  373. * Options:
  374. * vertical: [top, center, bottom]
  375. * horizontal: [left, middle, right].
  376. */
  377. anchorOrigin: _propTypes4.default.origin,
  378. /**
  379. * If true, the popover will apply transitions when
  380. * it is added to the DOM.
  381. */
  382. animated: _propTypes2.default.bool,
  383. /**
  384. * Override the default animation component used.
  385. */
  386. animation: _propTypes2.default.func,
  387. /**
  388. * If true, the popover will hide when the anchor is scrolled off the screen.
  389. */
  390. autoCloseWhenOffScreen: _propTypes2.default.bool,
  391. /**
  392. * If true, the popover (potentially) ignores `targetOrigin`
  393. * and `anchorOrigin` to make itself fit on screen,
  394. * which is useful for mobile devices.
  395. */
  396. canAutoPosition: _propTypes2.default.bool,
  397. /**
  398. * The content of the popover.
  399. */
  400. children: _propTypes2.default.node,
  401. /**
  402. * The CSS class name of the root element.
  403. */
  404. className: _propTypes2.default.string,
  405. /**
  406. * Callback function fired when the popover is requested to be closed.
  407. *
  408. * @param {string} reason The reason for the close request. Possibles values
  409. * are 'clickAway' and 'offScreen'.
  410. */
  411. onRequestClose: _propTypes2.default.func,
  412. /**
  413. * If true, the popover is visible.
  414. */
  415. open: _propTypes2.default.bool,
  416. /**
  417. * Represents the parent scrollable container.
  418. * It can be an element or a string like `window`.
  419. */
  420. scrollableContainer: _propTypes2.default.oneOfType([_propTypes2.default.object, _propTypes2.default.string]),
  421. /**
  422. * Override the inline-styles of the root element.
  423. */
  424. style: _propTypes2.default.object,
  425. /**
  426. * This is the point on the popover which will attach to
  427. * the anchor's origin.
  428. * Options:
  429. * vertical: [top, center, bottom]
  430. * horizontal: [left, middle, right].
  431. */
  432. targetOrigin: _propTypes4.default.origin,
  433. /**
  434. * If true, the popover will render on top of an invisible
  435. * layer, which will prevent clicks to the underlying
  436. * elements, and trigger an `onRequestClose('clickAway')` call.
  437. */
  438. useLayerForClickAway: _propTypes2.default.bool,
  439. /**
  440. * The zDepth of the popover.
  441. */
  442. zDepth: _propTypes4.default.zDepth
  443. } : {};
  444. exports.default = Popover;