mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-25 09:55:15 +00:00
479 lines
16 KiB
JavaScript
479 lines
16 KiB
JavaScript
|
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
||
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
||
|
import _assertThisInitialized from "@babel/runtime/helpers/esm/assertThisInitialized";
|
||
|
import _inheritsLoose from "@babel/runtime/helpers/esm/inheritsLoose";
|
||
|
|
||
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||
|
|
||
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||
|
|
||
|
import React from 'react';
|
||
|
import PropTypes from 'prop-types';
|
||
|
import classNames from 'classnames';
|
||
|
import Portal from './Portal';
|
||
|
import Fade from './Fade';
|
||
|
import { getOriginalBodyPadding, conditionallyUpdateScrollbar, setScrollbarWidth, mapToCssModules, omit, focusableElements, TransitionTimeouts, keyCodes, targetPropType, getTarget } from './utils';
|
||
|
|
||
|
function noop() {}
|
||
|
|
||
|
var FadePropTypes = PropTypes.shape(Fade.propTypes);
|
||
|
var propTypes = {
|
||
|
isOpen: PropTypes.bool,
|
||
|
autoFocus: PropTypes.bool,
|
||
|
centered: PropTypes.bool,
|
||
|
scrollable: PropTypes.bool,
|
||
|
size: PropTypes.string,
|
||
|
toggle: PropTypes.func,
|
||
|
keyboard: PropTypes.bool,
|
||
|
role: PropTypes.string,
|
||
|
labelledBy: PropTypes.string,
|
||
|
backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
|
||
|
onEnter: PropTypes.func,
|
||
|
onExit: PropTypes.func,
|
||
|
onOpened: PropTypes.func,
|
||
|
onClosed: PropTypes.func,
|
||
|
children: PropTypes.node,
|
||
|
className: PropTypes.string,
|
||
|
wrapClassName: PropTypes.string,
|
||
|
modalClassName: PropTypes.string,
|
||
|
backdropClassName: PropTypes.string,
|
||
|
contentClassName: PropTypes.string,
|
||
|
external: PropTypes.node,
|
||
|
fade: PropTypes.bool,
|
||
|
cssModule: PropTypes.object,
|
||
|
zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||
|
backdropTransition: FadePropTypes,
|
||
|
modalTransition: FadePropTypes,
|
||
|
innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.func]),
|
||
|
unmountOnClose: PropTypes.bool,
|
||
|
returnFocusAfterClose: PropTypes.bool,
|
||
|
container: targetPropType,
|
||
|
trapFocus: PropTypes.bool
|
||
|
};
|
||
|
var propsToOmit = Object.keys(propTypes);
|
||
|
var defaultProps = {
|
||
|
isOpen: false,
|
||
|
autoFocus: true,
|
||
|
centered: false,
|
||
|
scrollable: false,
|
||
|
role: 'dialog',
|
||
|
backdrop: true,
|
||
|
keyboard: true,
|
||
|
zIndex: 1050,
|
||
|
fade: true,
|
||
|
onOpened: noop,
|
||
|
onClosed: noop,
|
||
|
modalTransition: {
|
||
|
timeout: TransitionTimeouts.Modal
|
||
|
},
|
||
|
backdropTransition: {
|
||
|
mountOnEnter: true,
|
||
|
timeout: TransitionTimeouts.Fade // uses standard fade transition
|
||
|
|
||
|
},
|
||
|
unmountOnClose: true,
|
||
|
returnFocusAfterClose: true,
|
||
|
container: 'body',
|
||
|
trapFocus: false
|
||
|
};
|
||
|
|
||
|
var Modal = /*#__PURE__*/function (_React$Component) {
|
||
|
_inheritsLoose(Modal, _React$Component);
|
||
|
|
||
|
function Modal(props) {
|
||
|
var _this;
|
||
|
|
||
|
_this = _React$Component.call(this, props) || this;
|
||
|
_this._element = null;
|
||
|
_this._originalBodyPadding = null;
|
||
|
_this.getFocusableChildren = _this.getFocusableChildren.bind(_assertThisInitialized(_this));
|
||
|
_this.handleBackdropClick = _this.handleBackdropClick.bind(_assertThisInitialized(_this));
|
||
|
_this.handleBackdropMouseDown = _this.handleBackdropMouseDown.bind(_assertThisInitialized(_this));
|
||
|
_this.handleEscape = _this.handleEscape.bind(_assertThisInitialized(_this));
|
||
|
_this.handleStaticBackdropAnimation = _this.handleStaticBackdropAnimation.bind(_assertThisInitialized(_this));
|
||
|
_this.handleTab = _this.handleTab.bind(_assertThisInitialized(_this));
|
||
|
_this.onOpened = _this.onOpened.bind(_assertThisInitialized(_this));
|
||
|
_this.onClosed = _this.onClosed.bind(_assertThisInitialized(_this));
|
||
|
_this.manageFocusAfterClose = _this.manageFocusAfterClose.bind(_assertThisInitialized(_this));
|
||
|
_this.clearBackdropAnimationTimeout = _this.clearBackdropAnimationTimeout.bind(_assertThisInitialized(_this));
|
||
|
_this.trapFocus = _this.trapFocus.bind(_assertThisInitialized(_this));
|
||
|
_this.state = {
|
||
|
isOpen: false,
|
||
|
showStaticBackdropAnimation: false
|
||
|
};
|
||
|
return _this;
|
||
|
}
|
||
|
|
||
|
var _proto = Modal.prototype;
|
||
|
|
||
|
_proto.componentDidMount = function componentDidMount() {
|
||
|
var _this$props = this.props,
|
||
|
isOpen = _this$props.isOpen,
|
||
|
autoFocus = _this$props.autoFocus,
|
||
|
onEnter = _this$props.onEnter;
|
||
|
|
||
|
if (isOpen) {
|
||
|
this.init();
|
||
|
this.setState({
|
||
|
isOpen: true
|
||
|
});
|
||
|
|
||
|
if (autoFocus) {
|
||
|
this.setFocus();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (onEnter) {
|
||
|
onEnter();
|
||
|
} // traps focus inside the Modal, even if the browser address bar is focused
|
||
|
|
||
|
|
||
|
document.addEventListener('focus', this.trapFocus, true);
|
||
|
this._isMounted = true;
|
||
|
};
|
||
|
|
||
|
_proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
|
||
|
if (this.props.isOpen && !prevProps.isOpen) {
|
||
|
this.init();
|
||
|
this.setState({
|
||
|
isOpen: true
|
||
|
}); // let render() renders Modal Dialog first
|
||
|
|
||
|
return;
|
||
|
} // now Modal Dialog is rendered and we can refer this._element and this._dialog
|
||
|
|
||
|
|
||
|
if (this.props.autoFocus && this.state.isOpen && !prevState.isOpen) {
|
||
|
this.setFocus();
|
||
|
}
|
||
|
|
||
|
if (this._element && prevProps.zIndex !== this.props.zIndex) {
|
||
|
this._element.style.zIndex = this.props.zIndex;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.componentWillUnmount = function componentWillUnmount() {
|
||
|
this.clearBackdropAnimationTimeout();
|
||
|
|
||
|
if (this.props.onExit) {
|
||
|
this.props.onExit();
|
||
|
}
|
||
|
|
||
|
if (this._element) {
|
||
|
this.destroy();
|
||
|
|
||
|
if (this.props.isOpen || this.state.isOpen) {
|
||
|
this.close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
document.removeEventListener('focus', this.trapFocus, true);
|
||
|
this._isMounted = false;
|
||
|
};
|
||
|
|
||
|
_proto.trapFocus = function trapFocus(ev) {
|
||
|
if (!this.props.trapFocus) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!this._element) //element is not attached
|
||
|
return;
|
||
|
if (this._dialog && this._dialog.parentNode === ev.target) // initial focus when the Modal is opened
|
||
|
return;
|
||
|
if (this.modalIndex < Modal.openCount - 1) // last opened modal
|
||
|
return;
|
||
|
var children = this.getFocusableChildren();
|
||
|
|
||
|
for (var i = 0; i < children.length; i++) {
|
||
|
// focus is already inside the Modal
|
||
|
if (children[i] === ev.target) return;
|
||
|
}
|
||
|
|
||
|
if (children.length > 0) {
|
||
|
// otherwise focus the first focusable element in the Modal
|
||
|
ev.preventDefault();
|
||
|
ev.stopPropagation();
|
||
|
children[0].focus();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.onOpened = function onOpened(node, isAppearing) {
|
||
|
this.props.onOpened();
|
||
|
(this.props.modalTransition.onEntered || noop)(node, isAppearing);
|
||
|
};
|
||
|
|
||
|
_proto.onClosed = function onClosed(node) {
|
||
|
var unmountOnClose = this.props.unmountOnClose; // so all methods get called before it is unmounted
|
||
|
|
||
|
this.props.onClosed();
|
||
|
(this.props.modalTransition.onExited || noop)(node);
|
||
|
|
||
|
if (unmountOnClose) {
|
||
|
this.destroy();
|
||
|
}
|
||
|
|
||
|
this.close();
|
||
|
|
||
|
if (this._isMounted) {
|
||
|
this.setState({
|
||
|
isOpen: false
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.setFocus = function setFocus() {
|
||
|
if (this._dialog && this._dialog.parentNode && typeof this._dialog.parentNode.focus === 'function') {
|
||
|
this._dialog.parentNode.focus();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.getFocusableChildren = function getFocusableChildren() {
|
||
|
return this._element.querySelectorAll(focusableElements.join(', '));
|
||
|
};
|
||
|
|
||
|
_proto.getFocusedChild = function getFocusedChild() {
|
||
|
var currentFocus;
|
||
|
var focusableChildren = this.getFocusableChildren();
|
||
|
|
||
|
try {
|
||
|
currentFocus = document.activeElement;
|
||
|
} catch (err) {
|
||
|
currentFocus = focusableChildren[0];
|
||
|
}
|
||
|
|
||
|
return currentFocus;
|
||
|
} // not mouseUp because scrollbar fires it, shouldn't close when user scrolls
|
||
|
;
|
||
|
|
||
|
_proto.handleBackdropClick = function handleBackdropClick(e) {
|
||
|
if (e.target === this._mouseDownElement) {
|
||
|
e.stopPropagation();
|
||
|
var backdrop = this._dialog ? this._dialog.parentNode : null;
|
||
|
|
||
|
if (backdrop && e.target === backdrop && this.props.backdrop === 'static') {
|
||
|
this.handleStaticBackdropAnimation();
|
||
|
}
|
||
|
|
||
|
if (!this.props.isOpen || this.props.backdrop !== true) return;
|
||
|
|
||
|
if (backdrop && e.target === backdrop && this.props.toggle) {
|
||
|
this.props.toggle(e);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.handleTab = function handleTab(e) {
|
||
|
if (e.which !== 9) return;
|
||
|
if (this.modalIndex < Modal.openCount - 1) return; // last opened modal
|
||
|
|
||
|
var focusableChildren = this.getFocusableChildren();
|
||
|
var totalFocusable = focusableChildren.length;
|
||
|
if (totalFocusable === 0) return;
|
||
|
var currentFocus = this.getFocusedChild();
|
||
|
var focusedIndex = 0;
|
||
|
|
||
|
for (var i = 0; i < totalFocusable; i += 1) {
|
||
|
if (focusableChildren[i] === currentFocus) {
|
||
|
focusedIndex = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (e.shiftKey && focusedIndex === 0) {
|
||
|
e.preventDefault();
|
||
|
focusableChildren[totalFocusable - 1].focus();
|
||
|
} else if (!e.shiftKey && focusedIndex === totalFocusable - 1) {
|
||
|
e.preventDefault();
|
||
|
focusableChildren[0].focus();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.handleBackdropMouseDown = function handleBackdropMouseDown(e) {
|
||
|
this._mouseDownElement = e.target;
|
||
|
};
|
||
|
|
||
|
_proto.handleEscape = function handleEscape(e) {
|
||
|
if (this.props.isOpen && e.keyCode === keyCodes.esc && this.props.toggle) {
|
||
|
if (this.props.keyboard) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
this.props.toggle(e);
|
||
|
} else if (this.props.backdrop === 'static') {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
this.handleStaticBackdropAnimation();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.handleStaticBackdropAnimation = function handleStaticBackdropAnimation() {
|
||
|
var _this2 = this;
|
||
|
|
||
|
this.clearBackdropAnimationTimeout();
|
||
|
this.setState({
|
||
|
showStaticBackdropAnimation: true
|
||
|
});
|
||
|
this._backdropAnimationTimeout = setTimeout(function () {
|
||
|
_this2.setState({
|
||
|
showStaticBackdropAnimation: false
|
||
|
});
|
||
|
}, 100);
|
||
|
};
|
||
|
|
||
|
_proto.init = function init() {
|
||
|
try {
|
||
|
this._triggeringElement = document.activeElement;
|
||
|
} catch (err) {
|
||
|
this._triggeringElement = null;
|
||
|
}
|
||
|
|
||
|
if (!this._element) {
|
||
|
this._element = document.createElement('div');
|
||
|
|
||
|
this._element.setAttribute('tabindex', '-1');
|
||
|
|
||
|
this._element.style.position = 'relative';
|
||
|
this._element.style.zIndex = this.props.zIndex;
|
||
|
this._mountContainer = getTarget(this.props.container);
|
||
|
|
||
|
this._mountContainer.appendChild(this._element);
|
||
|
}
|
||
|
|
||
|
this._originalBodyPadding = getOriginalBodyPadding();
|
||
|
conditionallyUpdateScrollbar();
|
||
|
|
||
|
if (Modal.openCount === 0) {
|
||
|
document.body.className = classNames(document.body.className, mapToCssModules('modal-open', this.props.cssModule));
|
||
|
}
|
||
|
|
||
|
this.modalIndex = Modal.openCount;
|
||
|
Modal.openCount += 1;
|
||
|
};
|
||
|
|
||
|
_proto.destroy = function destroy() {
|
||
|
if (this._element) {
|
||
|
this._mountContainer.removeChild(this._element);
|
||
|
|
||
|
this._element = null;
|
||
|
}
|
||
|
|
||
|
this.manageFocusAfterClose();
|
||
|
};
|
||
|
|
||
|
_proto.manageFocusAfterClose = function manageFocusAfterClose() {
|
||
|
if (this._triggeringElement) {
|
||
|
var returnFocusAfterClose = this.props.returnFocusAfterClose;
|
||
|
if (this._triggeringElement.focus && returnFocusAfterClose) this._triggeringElement.focus();
|
||
|
this._triggeringElement = null;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
_proto.close = function close() {
|
||
|
if (Modal.openCount <= 1) {
|
||
|
var modalOpenClassName = mapToCssModules('modal-open', this.props.cssModule); // Use regex to prevent matching `modal-open` as part of a different class, e.g. `my-modal-opened`
|
||
|
|
||
|
var modalOpenClassNameRegex = new RegExp("(^| )" + modalOpenClassName + "( |$)");
|
||
|
document.body.className = document.body.className.replace(modalOpenClassNameRegex, ' ').trim();
|
||
|
}
|
||
|
|
||
|
this.manageFocusAfterClose();
|
||
|
Modal.openCount = Math.max(0, Modal.openCount - 1);
|
||
|
setScrollbarWidth(this._originalBodyPadding);
|
||
|
};
|
||
|
|
||
|
_proto.renderModalDialog = function renderModalDialog() {
|
||
|
var _classNames,
|
||
|
_this3 = this;
|
||
|
|
||
|
var attributes = omit(this.props, propsToOmit);
|
||
|
var dialogBaseClass = 'modal-dialog';
|
||
|
return /*#__PURE__*/React.createElement("div", _extends({}, attributes, {
|
||
|
className: mapToCssModules(classNames(dialogBaseClass, this.props.className, (_classNames = {}, _classNames["modal-" + this.props.size] = this.props.size, _classNames[dialogBaseClass + "-centered"] = this.props.centered, _classNames[dialogBaseClass + "-scrollable"] = this.props.scrollable, _classNames)), this.props.cssModule),
|
||
|
role: "document",
|
||
|
ref: function ref(c) {
|
||
|
_this3._dialog = c;
|
||
|
}
|
||
|
}), /*#__PURE__*/React.createElement("div", {
|
||
|
className: mapToCssModules(classNames('modal-content', this.props.contentClassName), this.props.cssModule)
|
||
|
}, this.props.children));
|
||
|
};
|
||
|
|
||
|
_proto.render = function render() {
|
||
|
var unmountOnClose = this.props.unmountOnClose;
|
||
|
|
||
|
if (!!this._element && (this.state.isOpen || !unmountOnClose)) {
|
||
|
var isModalHidden = !!this._element && !this.state.isOpen && !unmountOnClose;
|
||
|
this._element.style.display = isModalHidden ? 'none' : 'block';
|
||
|
var _this$props2 = this.props,
|
||
|
wrapClassName = _this$props2.wrapClassName,
|
||
|
modalClassName = _this$props2.modalClassName,
|
||
|
backdropClassName = _this$props2.backdropClassName,
|
||
|
cssModule = _this$props2.cssModule,
|
||
|
isOpen = _this$props2.isOpen,
|
||
|
backdrop = _this$props2.backdrop,
|
||
|
role = _this$props2.role,
|
||
|
labelledBy = _this$props2.labelledBy,
|
||
|
external = _this$props2.external,
|
||
|
innerRef = _this$props2.innerRef;
|
||
|
var modalAttributes = {
|
||
|
onClick: this.handleBackdropClick,
|
||
|
onMouseDown: this.handleBackdropMouseDown,
|
||
|
onKeyUp: this.handleEscape,
|
||
|
onKeyDown: this.handleTab,
|
||
|
style: {
|
||
|
display: 'block'
|
||
|
},
|
||
|
'aria-labelledby': labelledBy,
|
||
|
role: role,
|
||
|
tabIndex: '-1'
|
||
|
};
|
||
|
var hasTransition = this.props.fade;
|
||
|
|
||
|
var modalTransition = _objectSpread(_objectSpread(_objectSpread({}, Fade.defaultProps), this.props.modalTransition), {}, {
|
||
|
baseClass: hasTransition ? this.props.modalTransition.baseClass : '',
|
||
|
timeout: hasTransition ? this.props.modalTransition.timeout : 0
|
||
|
});
|
||
|
|
||
|
var backdropTransition = _objectSpread(_objectSpread(_objectSpread({}, Fade.defaultProps), this.props.backdropTransition), {}, {
|
||
|
baseClass: hasTransition ? this.props.backdropTransition.baseClass : '',
|
||
|
timeout: hasTransition ? this.props.backdropTransition.timeout : 0
|
||
|
});
|
||
|
|
||
|
var Backdrop = backdrop && (hasTransition ? /*#__PURE__*/React.createElement(Fade, _extends({}, backdropTransition, {
|
||
|
in: isOpen && !!backdrop,
|
||
|
cssModule: cssModule,
|
||
|
className: mapToCssModules(classNames('modal-backdrop', backdropClassName), cssModule)
|
||
|
})) : /*#__PURE__*/React.createElement("div", {
|
||
|
className: mapToCssModules(classNames('modal-backdrop', 'show', backdropClassName), cssModule)
|
||
|
}));
|
||
|
return /*#__PURE__*/React.createElement(Portal, {
|
||
|
node: this._element
|
||
|
}, /*#__PURE__*/React.createElement("div", {
|
||
|
className: mapToCssModules(wrapClassName)
|
||
|
}, /*#__PURE__*/React.createElement(Fade, _extends({}, modalAttributes, modalTransition, {
|
||
|
in: isOpen,
|
||
|
onEntered: this.onOpened,
|
||
|
onExited: this.onClosed,
|
||
|
cssModule: cssModule,
|
||
|
className: mapToCssModules(classNames('modal', modalClassName, this.state.showStaticBackdropAnimation && 'modal-static'), cssModule),
|
||
|
innerRef: innerRef
|
||
|
}), external, this.renderModalDialog()), Backdrop));
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
_proto.clearBackdropAnimationTimeout = function clearBackdropAnimationTimeout() {
|
||
|
if (this._backdropAnimationTimeout) {
|
||
|
clearTimeout(this._backdropAnimationTimeout);
|
||
|
this._backdropAnimationTimeout = undefined;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return Modal;
|
||
|
}(React.Component);
|
||
|
|
||
|
Modal.propTypes = propTypes;
|
||
|
Modal.defaultProps = defaultProps;
|
||
|
Modal.openCount = 0;
|
||
|
export default Modal;
|