@spotware/react-tether
Version:
Drop content anywhere on the page.
312 lines (258 loc) • 10.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _tether = require('tether');
var _tether2 = _interopRequireDefault(_tether);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
if (!_tether2.default) {
console.error('It looks like Tether has not been included. Please load this dependency first https://github.com/HubSpot/tether');
}
var hasCreatePortal = _reactDom2.default.createPortal !== undefined;
var renderElementToPropTypes = [_propTypes2.default.string, _propTypes2.default.shape({
appendChild: _propTypes2.default.func.isRequired
})];
var childrenPropType = function childrenPropType(_ref, propName, componentName) {
var children = _ref.children;
var childCount = _react.Children.count(children);
if (childCount <= 0) {
return new Error(componentName + ' expects at least one child to use as the target element.');
}
if (childCount > 2) {
return new Error('Only a max of two children allowed in ' + componentName + '.');
}
};
var attachmentPositions = ['auto auto', 'top left', 'top center', 'top right', 'middle left', 'middle center', 'middle right', 'bottom left', 'bottom center', 'bottom right'];
var TetherComponent = function (_Component) {
_inherits(TetherComponent, _Component);
function TetherComponent(props) {
_classCallCheck(this, TetherComponent);
var _this = _possibleConstructorReturn(this, (TetherComponent.__proto__ || Object.getPrototypeOf(TetherComponent)).call(this, props));
_this._targetNode = null;
_this._elementParentNode = null;
_this._tether = null;
var elementComponent = _react.Children.toArray(props.children)[1];
if (elementComponent) {
_this._createContainer();
}
return _this;
}
_createClass(TetherComponent, [{
key: 'componentWillUpdate',
value: function componentWillUpdate(_ref2) {
var children = _ref2.children;
var elementComponent = _react.Children.toArray(children)[1];
if (elementComponent) {
this._createContainer();
}
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
this._targetNode = _reactDom2.default.findDOMNode(this);
this._update();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this._targetNode = _reactDom2.default.findDOMNode(this);
this._update();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this._destroy();
}
}, {
key: 'getTetherInstance',
value: function getTetherInstance() {
return this._tether;
}
}, {
key: 'disable',
value: function disable() {
this._tether.disable();
}
}, {
key: 'enable',
value: function enable() {
this._tether.enable();
}
}, {
key: 'on',
value: function on(event, handler, ctx) {
this._tether.on(event, handler, ctx);
}
}, {
key: 'once',
value: function once(event, handler, ctx) {
this._tether.once(event, handler, ctx);
}
}, {
key: 'off',
value: function off(event, handler) {
this._tether.off(event, handler);
}
}, {
key: 'position',
value: function position() {
this._tether.position();
}
}, {
key: '_registerEventListeners',
value: function _registerEventListeners() {
var _this2 = this,
_arguments = arguments;
this.on('update', function () {
return _this2.props.onUpdate && _this2.props.onUpdate.apply(_this2, _arguments);
});
this.on('repositioned', function () {
return _this2.props.onRepositioned && _this2.props.onRepositioned.apply(_this2, _arguments);
});
}
}, {
key: '_destroy',
value: function _destroy() {
if (this._elementParentNode) {
if (!hasCreatePortal) {
_reactDom2.default.unmountComponentAtNode(this._elementParentNode);
}
this._elementParentNode.parentNode.removeChild(this._elementParentNode);
}
if (this._tether) {
this._tether.destroy();
}
this._elementParentNode = null;
this._tether = null;
}
}, {
key: '_createContainer',
value: function _createContainer() {
var renderElementTag = this.props.renderElementTag;
// Create element node container if it hasn't been yet
if (!this._elementParentNode) {
// Create a node that we can stick our content Component in
this._elementParentNode = document.createElement(renderElementTag);
// Append node to the render node
this._renderNode.appendChild(this._elementParentNode);
}
}
}, {
key: '_update',
value: function _update() {
var _this3 = this;
var children = this.props.children;
var elementComponent = _react.Children.toArray(children)[1];
// If no element component provided, bail out
if (!elementComponent) {
// Destroy Tether element if it has been created
if (this._tether) {
this._destroy();
}
return;
}
if (hasCreatePortal) {
this._updateTether();
} else {
// Render element component into the DOM
_reactDom2.default.unstable_renderSubtreeIntoContainer(this, elementComponent, this._elementParentNode, function () {
// If we're not destroyed, update Tether once the subtree has finished rendering
if (_this3._elementParentNode) {
_this3._updateTether();
}
});
}
}
}, {
key: '_updateTether',
value: function _updateTether() {
var _this4 = this;
var _props = this.props,
children = _props.children,
renderElementTag = _props.renderElementTag,
renderElementTo = _props.renderElementTo,
id = _props.id,
className = _props.className,
style = _props.style,
options = _objectWithoutProperties(_props, ['children', 'renderElementTag', 'renderElementTo', 'id', 'className', 'style']);
var tetherOptions = _extends({
target: this._targetNode,
element: this._elementParentNode
}, options);
if (id) {
this._elementParentNode.id = id;
}
if (className) {
this._elementParentNode.className = className;
}
if (style) {
Object.keys(style).forEach(function (key) {
_this4._elementParentNode.style[key] = style[key];
});
}
if (this._tether) {
this._tether.setOptions(tetherOptions);
} else {
this._tether = new _tether2.default(tetherOptions);
this._registerEventListeners();
}
this._tether.position();
}
}, {
key: 'render',
value: function render() {
var children = this.props.children;
var elementComponent = _react.Children.toArray(children)[1];
if (!hasCreatePortal || !elementComponent) {
return _react.Children.toArray(children)[0];
}
return [_react.Children.toArray(children)[0], _reactDom2.default.createPortal(elementComponent, this._elementParentNode)];
}
}, {
key: '_renderNode',
get: function get() {
var renderElementTo = this.props.renderElementTo;
if (typeof renderElementTo === 'string') {
return document.querySelector(renderElementTo);
}
return renderElementTo || document.body;
}
}]);
return TetherComponent;
}(_react.Component);
TetherComponent.propTypes = {
renderElementTag: _propTypes2.default.string,
renderElementTo: _propTypes2.default.oneOfType(renderElementToPropTypes),
attachment: _propTypes2.default.oneOf(attachmentPositions).isRequired,
targetAttachment: _propTypes2.default.oneOf(attachmentPositions),
offset: _propTypes2.default.string,
targetOffset: _propTypes2.default.string,
targetModifier: _propTypes2.default.string,
enabled: _propTypes2.default.bool,
classes: _propTypes2.default.object,
classPrefix: _propTypes2.default.string,
optimizations: _propTypes2.default.object,
constraints: _propTypes2.default.array,
id: _propTypes2.default.string,
className: _propTypes2.default.string,
style: _propTypes2.default.object,
onUpdate: _propTypes2.default.func,
onRepositioned: _propTypes2.default.func,
children: childrenPropType
};
TetherComponent.defaultProps = {
renderElementTag: 'div',
renderElementTo: null
};
exports.default = TetherComponent;