UNPKG

react-float-anchor

Version:

React component for positioning an element aligned to another

374 lines (294 loc) 15.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _fromEventsWithOptions = _interopRequireDefault(require("./lib/fromEventsWithOptions")); var _LifecycleHelper = _interopRequireDefault(require("./LifecycleHelper")); var _kefir = _interopRequireDefault(require("kefir")); var _kefirBus = _interopRequireDefault(require("kefir-bus")); var React = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); var _propTypes = _interopRequireDefault(require("prop-types")); var _containByScreen = require("contain-by-screen"); var _isEqual = _interopRequireDefault(require("lodash/isEqual")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } var requestAnimationFrame = global.requestAnimationFrame || function (cb) { return Promise.resolve().then(cb); }; // Context is used so that when a FloatAnchor has reposition() called on it, // all of its descendant FloatAnchor elements reposition too. var FloatAnchorContext = /*#__PURE__*/React.createContext(null); var FloatAnchor = /*#__PURE__*/function (_React$Component) { (0, _inherits2["default"])(FloatAnchor, _React$Component); var _super = _createSuper(FloatAnchor); function FloatAnchor() { var _this; (0, _classCallCheck2["default"])(this, FloatAnchor); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _super.call.apply(_super, [this].concat(args)); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "state", { choice: null, floatNode: null }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_portalEl", void 0); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_portalRemoval", (0, _kefirBus["default"])()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_unmount", (0, _kefirBus["default"])()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_childContext", { repositionEvents: (0, _kefirBus["default"])(), repositionAsyncQueued: false, repositionAsyncEvents: (0, _kefirBus["default"])() }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_anchorRef", null); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_setAnchorRef", function (anchorRef) { _this._anchorRef = anchorRef; var portalEl = _this._portalEl; if (portalEl) { // rfaAnchor is also set in _mountPortal. This line is necessary in the case that // the anchorRef is updated. portalEl.rfaAnchor = anchorRef ? anchorRef : undefined; } }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_mountPortalEl", function () { var portalEl = _this._portalEl; /*:: if (!portalEl) throw new Error(); */ if (portalEl.parentElement) { throw new Error('Should not happen: portalEl already in page'); } var anchorRef = _this._getAnchorRef(); if (!anchorRef) throw new Error('ReactFloatAnchor missing anchorRef element'); portalEl.rfaAnchor = anchorRef; var target = _this.props.parentElement || document.body || document.documentElement; /*:: if (!target) throw new Error(); */ target.appendChild(portalEl); _kefir["default"].merge([_kefir["default"].fromEvents(window, 'resize'), (0, _fromEventsWithOptions["default"])(window, 'scroll', { capture: true, passive: true }).filter(function (event) { var anchorRef = _this._getAnchorRef(); return anchorRef && event.target.contains(anchorRef); })]).takeUntilBy(_this._portalRemoval).onValue(function () { _this.repositionAsync(); }).onEnd(function () { portalEl.rfaAnchor = undefined; /*:: if (!portalEl.parentElement) throw new Error(); */ portalEl.parentElement.removeChild(portalEl); }); }); return _this; } (0, _createClass2["default"])(FloatAnchor, [{ key: "_getAnchorRef", value: function _getAnchorRef() { return typeof this.props.anchor === 'function' ? this._anchorRef : this.props.anchor; } }, { key: "_getOrCreatePortalEl", value: function _getOrCreatePortalEl() { var portalEl_firstCheck = this._portalEl; if (portalEl_firstCheck) { return portalEl_firstCheck; } var portalEl = this._portalEl = document.createElement('div'); portalEl.className = this.props.floatContainerClassName || ''; portalEl.style.zIndex = String(this.props.zIndex); portalEl.style.position = 'fixed'; return portalEl; } }, { key: "componentDidMount", value: function componentDidMount() { var _this2 = this; var parentCtx = this.context; if (parentCtx) { parentCtx.repositionEvents.takeUntilBy(this._unmount).onValue(function () { return _this2.reposition(); }); parentCtx.repositionAsyncEvents.takeUntilBy(this._unmount).onValue(function () { if (!_this2._childContext.repositionAsyncQueued) { _this2._childContext.repositionAsyncQueued = true; _this2._childContext.repositionAsyncEvents.value(null); } }); if (parentCtx.repositionAsyncQueued) { this._childContext.repositionAsyncQueued = true; this._childContext.repositionAsyncEvents.value(null); } } if (this.state.floatNode != null) { // We need to reposition after the page has had its layout done. this.repositionAsync(); } } }, { key: "shouldComponentUpdate", value: function shouldComponentUpdate(nextProps, nextState) { // If the only thing changed is state.choice *and* typeof props.float !== 'function', don't re-render. // If nothing has changed, allow the re-render so we keep the same behavior on a plain forceUpdate of a parent. // TODO in next major version, don't re-render when nothing has changed. if (typeof nextProps["float"] !== 'function' && this.state.choice !== nextState.choice && this.props.anchor === nextProps.anchor && this.props.parentElement === nextProps.parentElement && this.props["float"] === nextProps["float"] && this.props.options === nextProps.options && this.props.zIndex === nextProps.zIndex && this.props.floatContainerClassName === nextProps.floatContainerClassName) { return false; } return true; } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { var portalEl = this._portalEl; if (portalEl) { if (this.state.floatNode == null) { this._portalRemoval.value(null); } else { if (prevProps.parentElement !== this.props.parentElement) { this._portalRemoval.value(null); this._mountPortalEl(); } if (prevProps.floatContainerClassName !== this.props.floatContainerClassName) { portalEl.className = this.props.floatContainerClassName || ''; } if (prevProps.zIndex !== this.props.zIndex) { portalEl.style.zIndex = String(this.props.zIndex); } // If anchor is an HTMLElement and has changed, then update portalEl.rfaAnchor if (typeof this.props.anchor !== 'function' && prevProps.anchor !== this.props.anchor) { portalEl.rfaAnchor = this.props.anchor; this.repositionAsync(); } else if ( // If this re-render happened because of a change in position choice, don't reposition again now. prevState.floatNode !== this.state.floatNode && prevState.choice === this.state.choice || !(0, _isEqual["default"])(prevProps.options, this.props.options)) { this.repositionAsync(); } } } else { if (this.state.floatNode != null) { throw new Error('Should not happen: portalEl was null after rendering with float prop'); } } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this._portalRemoval.value(null); this._unmount.value(null); this._childContext.repositionEvents.end(); this._childContext.repositionAsyncEvents.end(); } // Repositions on the next animation frame. Automatically batches with other repositionAsync calls // in the same tree. }, { key: "repositionAsync", value: function repositionAsync() { var _this3 = this; // If we already have a repositionAsync queued up, there's no reason to queue another. if (this._childContext.repositionAsyncQueued) { return; } this._childContext.repositionAsyncQueued = true; this._childContext.repositionAsyncEvents.value(null); requestAnimationFrame(function () { // If our parent still has a repositionAsync queued up, then don't fire. // The parent may have queued up a repositionAsync in the time since this repositionAsync() was called. var parentCtx = _this3.context; if (!parentCtx || !parentCtx.repositionAsyncQueued) { // Make sure we still have a repositionAsync queued up. It could be that reposition() has been called // in the time since repositionAsync(). if (_this3._childContext.repositionAsyncQueued) { _this3._childContext.repositionAsyncQueued = false; _this3.reposition(); } } }); } }, { key: "reposition", value: function reposition() { // Only clear our repositionAsyncQueued flag if we're not reflecting our parent's true value. var parentCtx = this.context; if (!parentCtx || !parentCtx.repositionAsyncQueued) { this._childContext.repositionAsyncQueued = false; } var portalEl = this._portalEl; var anchorRef = this._getAnchorRef(); if (portalEl && portalEl.parentElement && anchorRef) { var _choice = (0, _containByScreen.containByScreen)(portalEl, anchorRef, this.props.options || {}); if (!(0, _isEqual["default"])(this.state.choice, _choice)) { this.setState({ choice: _choice }); } // Make any child FloatAnchors reposition this._childContext.repositionEvents.value(null); } } }, { key: "render", value: function render() { var anchor = this.props.anchor; var _float = this.state.floatNode; var floatPortal = null; if (_float != null) { var portalEl = this._getOrCreatePortalEl(); floatPortal = /*#__PURE__*/React.createElement(FloatAnchorContext.Provider, { value: this._childContext }, /*#__PURE__*/React.createElement(_LifecycleHelper["default"], { onMount: this._mountPortalEl }), /*#__PURE__*/(0, _reactDom.createPortal)(_float, portalEl)); } var anchorElement = typeof anchor === 'function' ? anchor(this._setAnchorRef) : null; return /*#__PURE__*/React.createElement(React.Fragment, null, anchorElement, floatPortal); } }], [{ key: "parentNodes", value: /*#__PURE__*/_regenerator["default"].mark(function parentNodes(node) { return _regenerator["default"].wrap(function parentNodes$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return node; case 2: if (node = node.rfaAnchor || node.parentNode) { _context.next = 0; break; } case 3: case "end": return _context.stop(); } } }, parentNodes); }) // This property is only used in the case that props.anchor is not an HTMLElement }, { key: "getDerivedStateFromProps", value: function getDerivedStateFromProps(props, state) { return { floatNode: typeof props["float"] === 'function' ? props["float"](state.choice) : props["float"] }; } }]); return FloatAnchor; }(React.Component); exports["default"] = FloatAnchor; (0, _defineProperty2["default"])(FloatAnchor, "propTypes", { anchor: _propTypes["default"].oneOfType([_propTypes["default"].func, _propTypes["default"].object]).isRequired, parentElement: _propTypes["default"].object, "float": _propTypes["default"].oneOfType([_propTypes["default"].node, _propTypes["default"].func]), options: _propTypes["default"].object, zIndex: _propTypes["default"].oneOfType([_propTypes["default"].number, _propTypes["default"].string]), floatContainerClassName: _propTypes["default"].string }); (0, _defineProperty2["default"])(FloatAnchor, "contextType", FloatAnchorContext); module.exports = exports.default; module.exports.default = exports.default; //# sourceMappingURL=index.js.map