mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-29 03:45:15 +00:00
431 lines
14 KiB
JavaScript
431 lines
14 KiB
JavaScript
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
||
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
||
|
|
||
|
/* eslint-disable @typescript-eslint/no-use-before-define, react/prop-types */
|
||
|
import activeElement from 'dom-helpers/activeElement';
|
||
|
import contains from 'dom-helpers/contains';
|
||
|
import canUseDOM from 'dom-helpers/canUseDOM';
|
||
|
import listen from 'dom-helpers/listen';
|
||
|
import PropTypes from 'prop-types';
|
||
|
import React, { useState, useRef, useCallback, useImperativeHandle, forwardRef, useEffect } from 'react';
|
||
|
import ReactDOM from 'react-dom';
|
||
|
import useMounted from '@restart/hooks/useMounted';
|
||
|
import useWillUnmount from '@restart/hooks/useWillUnmount';
|
||
|
import usePrevious from '@restart/hooks/usePrevious';
|
||
|
import useEventCallback from '@restart/hooks/useEventCallback';
|
||
|
import ModalManager from './ModalManager';
|
||
|
import useWaitForDOMRef from './useWaitForDOMRef';
|
||
|
var manager;
|
||
|
|
||
|
function getManager() {
|
||
|
if (!manager) manager = new ModalManager();
|
||
|
return manager;
|
||
|
}
|
||
|
|
||
|
function useModalManager(provided) {
|
||
|
var modalManager = provided || getManager();
|
||
|
var modal = useRef({
|
||
|
dialog: null,
|
||
|
backdrop: null
|
||
|
});
|
||
|
return Object.assign(modal.current, {
|
||
|
add: function add(container, className) {
|
||
|
return modalManager.add(modal.current, container, className);
|
||
|
},
|
||
|
remove: function remove() {
|
||
|
return modalManager.remove(modal.current);
|
||
|
},
|
||
|
isTopModal: function isTopModal() {
|
||
|
return modalManager.isTopModal(modal.current);
|
||
|
},
|
||
|
setDialogRef: useCallback(function (ref) {
|
||
|
modal.current.dialog = ref;
|
||
|
}, []),
|
||
|
setBackdropRef: useCallback(function (ref) {
|
||
|
modal.current.backdrop = ref;
|
||
|
}, [])
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var Modal = /*#__PURE__*/forwardRef(function (_ref, ref) {
|
||
|
var _ref$show = _ref.show,
|
||
|
show = _ref$show === void 0 ? false : _ref$show,
|
||
|
_ref$role = _ref.role,
|
||
|
role = _ref$role === void 0 ? 'dialog' : _ref$role,
|
||
|
className = _ref.className,
|
||
|
style = _ref.style,
|
||
|
children = _ref.children,
|
||
|
_ref$backdrop = _ref.backdrop,
|
||
|
backdrop = _ref$backdrop === void 0 ? true : _ref$backdrop,
|
||
|
_ref$keyboard = _ref.keyboard,
|
||
|
keyboard = _ref$keyboard === void 0 ? true : _ref$keyboard,
|
||
|
onBackdropClick = _ref.onBackdropClick,
|
||
|
onEscapeKeyDown = _ref.onEscapeKeyDown,
|
||
|
transition = _ref.transition,
|
||
|
backdropTransition = _ref.backdropTransition,
|
||
|
_ref$autoFocus = _ref.autoFocus,
|
||
|
autoFocus = _ref$autoFocus === void 0 ? true : _ref$autoFocus,
|
||
|
_ref$enforceFocus = _ref.enforceFocus,
|
||
|
enforceFocus = _ref$enforceFocus === void 0 ? true : _ref$enforceFocus,
|
||
|
_ref$restoreFocus = _ref.restoreFocus,
|
||
|
restoreFocus = _ref$restoreFocus === void 0 ? true : _ref$restoreFocus,
|
||
|
restoreFocusOptions = _ref.restoreFocusOptions,
|
||
|
renderDialog = _ref.renderDialog,
|
||
|
_ref$renderBackdrop = _ref.renderBackdrop,
|
||
|
renderBackdrop = _ref$renderBackdrop === void 0 ? function (props) {
|
||
|
return /*#__PURE__*/React.createElement("div", props);
|
||
|
} : _ref$renderBackdrop,
|
||
|
providedManager = _ref.manager,
|
||
|
containerRef = _ref.container,
|
||
|
containerClassName = _ref.containerClassName,
|
||
|
onShow = _ref.onShow,
|
||
|
_ref$onHide = _ref.onHide,
|
||
|
onHide = _ref$onHide === void 0 ? function () {} : _ref$onHide,
|
||
|
onExit = _ref.onExit,
|
||
|
onExited = _ref.onExited,
|
||
|
onExiting = _ref.onExiting,
|
||
|
onEnter = _ref.onEnter,
|
||
|
onEntering = _ref.onEntering,
|
||
|
onEntered = _ref.onEntered,
|
||
|
rest = _objectWithoutPropertiesLoose(_ref, ["show", "role", "className", "style", "children", "backdrop", "keyboard", "onBackdropClick", "onEscapeKeyDown", "transition", "backdropTransition", "autoFocus", "enforceFocus", "restoreFocus", "restoreFocusOptions", "renderDialog", "renderBackdrop", "manager", "container", "containerClassName", "onShow", "onHide", "onExit", "onExited", "onExiting", "onEnter", "onEntering", "onEntered"]);
|
||
|
|
||
|
var container = useWaitForDOMRef(containerRef);
|
||
|
var modal = useModalManager(providedManager);
|
||
|
var isMounted = useMounted();
|
||
|
var prevShow = usePrevious(show);
|
||
|
|
||
|
var _useState = useState(!show),
|
||
|
exited = _useState[0],
|
||
|
setExited = _useState[1];
|
||
|
|
||
|
var lastFocusRef = useRef(null);
|
||
|
useImperativeHandle(ref, function () {
|
||
|
return modal;
|
||
|
}, [modal]);
|
||
|
|
||
|
if (canUseDOM && !prevShow && show) {
|
||
|
lastFocusRef.current = activeElement();
|
||
|
}
|
||
|
|
||
|
if (!transition && !show && !exited) {
|
||
|
setExited(true);
|
||
|
} else if (show && exited) {
|
||
|
setExited(false);
|
||
|
}
|
||
|
|
||
|
var handleShow = useEventCallback(function () {
|
||
|
modal.add(container, containerClassName);
|
||
|
removeKeydownListenerRef.current = listen(document, 'keydown', handleDocumentKeyDown);
|
||
|
removeFocusListenerRef.current = listen(document, 'focus', // the timeout is necessary b/c this will run before the new modal is mounted
|
||
|
// and so steals focus from it
|
||
|
function () {
|
||
|
return setTimeout(handleEnforceFocus);
|
||
|
}, true);
|
||
|
|
||
|
if (onShow) {
|
||
|
onShow();
|
||
|
} // autofocus after onShow to not trigger a focus event for previous
|
||
|
// modals before this one is shown.
|
||
|
|
||
|
|
||
|
if (autoFocus) {
|
||
|
var currentActiveElement = activeElement(document);
|
||
|
|
||
|
if (modal.dialog && currentActiveElement && !contains(modal.dialog, currentActiveElement)) {
|
||
|
lastFocusRef.current = currentActiveElement;
|
||
|
modal.dialog.focus();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
var handleHide = useEventCallback(function () {
|
||
|
modal.remove();
|
||
|
removeKeydownListenerRef.current == null ? void 0 : removeKeydownListenerRef.current();
|
||
|
removeFocusListenerRef.current == null ? void 0 : removeFocusListenerRef.current();
|
||
|
|
||
|
if (restoreFocus) {
|
||
|
var _lastFocusRef$current;
|
||
|
|
||
|
// Support: <=IE11 doesn't support `focus()` on svg elements (RB: #917)
|
||
|
(_lastFocusRef$current = lastFocusRef.current) == null ? void 0 : _lastFocusRef$current.focus == null ? void 0 : _lastFocusRef$current.focus(restoreFocusOptions);
|
||
|
lastFocusRef.current = null;
|
||
|
}
|
||
|
}); // TODO: try and combine these effects: https://github.com/react-bootstrap/react-overlays/pull/794#discussion_r409954120
|
||
|
// Show logic when:
|
||
|
// - show is `true` _and_ `container` has resolved
|
||
|
|
||
|
useEffect(function () {
|
||
|
if (!show || !container) return;
|
||
|
handleShow();
|
||
|
}, [show, container,
|
||
|
/* should never change: */
|
||
|
handleShow]); // Hide cleanup logic when:
|
||
|
// - `exited` switches to true
|
||
|
// - component unmounts;
|
||
|
|
||
|
useEffect(function () {
|
||
|
if (!exited) return;
|
||
|
handleHide();
|
||
|
}, [exited, handleHide]);
|
||
|
useWillUnmount(function () {
|
||
|
handleHide();
|
||
|
}); // --------------------------------
|
||
|
|
||
|
var handleEnforceFocus = useEventCallback(function () {
|
||
|
if (!enforceFocus || !isMounted() || !modal.isTopModal()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var currentActiveElement = activeElement();
|
||
|
|
||
|
if (modal.dialog && currentActiveElement && !contains(modal.dialog, currentActiveElement)) {
|
||
|
modal.dialog.focus();
|
||
|
}
|
||
|
});
|
||
|
var handleBackdropClick = useEventCallback(function (e) {
|
||
|
if (e.target !== e.currentTarget) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
onBackdropClick == null ? void 0 : onBackdropClick(e);
|
||
|
|
||
|
if (backdrop === true) {
|
||
|
onHide();
|
||
|
}
|
||
|
});
|
||
|
var handleDocumentKeyDown = useEventCallback(function (e) {
|
||
|
if (keyboard && e.keyCode === 27 && modal.isTopModal()) {
|
||
|
onEscapeKeyDown == null ? void 0 : onEscapeKeyDown(e);
|
||
|
|
||
|
if (!e.defaultPrevented) {
|
||
|
onHide();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
var removeFocusListenerRef = useRef();
|
||
|
var removeKeydownListenerRef = useRef();
|
||
|
|
||
|
var handleHidden = function handleHidden() {
|
||
|
setExited(true);
|
||
|
|
||
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||
|
args[_key] = arguments[_key];
|
||
|
}
|
||
|
|
||
|
onExited == null ? void 0 : onExited.apply(void 0, args);
|
||
|
};
|
||
|
|
||
|
var Transition = transition;
|
||
|
|
||
|
if (!container || !(show || Transition && !exited)) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var dialogProps = _extends({
|
||
|
role: role,
|
||
|
ref: modal.setDialogRef,
|
||
|
// apparently only works on the dialog role element
|
||
|
'aria-modal': role === 'dialog' ? true : undefined
|
||
|
}, rest, {
|
||
|
style: style,
|
||
|
className: className,
|
||
|
tabIndex: -1
|
||
|
});
|
||
|
|
||
|
var dialog = renderDialog ? renderDialog(dialogProps) : /*#__PURE__*/React.createElement("div", dialogProps, /*#__PURE__*/React.cloneElement(children, {
|
||
|
role: 'document'
|
||
|
}));
|
||
|
|
||
|
if (Transition) {
|
||
|
dialog = /*#__PURE__*/React.createElement(Transition, {
|
||
|
appear: true,
|
||
|
unmountOnExit: true,
|
||
|
"in": !!show,
|
||
|
onExit: onExit,
|
||
|
onExiting: onExiting,
|
||
|
onExited: handleHidden,
|
||
|
onEnter: onEnter,
|
||
|
onEntering: onEntering,
|
||
|
onEntered: onEntered
|
||
|
}, dialog);
|
||
|
}
|
||
|
|
||
|
var backdropElement = null;
|
||
|
|
||
|
if (backdrop) {
|
||
|
var BackdropTransition = backdropTransition;
|
||
|
backdropElement = renderBackdrop({
|
||
|
ref: modal.setBackdropRef,
|
||
|
onClick: handleBackdropClick
|
||
|
});
|
||
|
|
||
|
if (BackdropTransition) {
|
||
|
backdropElement = /*#__PURE__*/React.createElement(BackdropTransition, {
|
||
|
appear: true,
|
||
|
"in": !!show
|
||
|
}, backdropElement);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/React.createElement(React.Fragment, null, backdropElement, dialog), container));
|
||
|
});
|
||
|
var propTypes = {
|
||
|
/**
|
||
|
* Set the visibility of the Modal
|
||
|
*/
|
||
|
show: PropTypes.bool,
|
||
|
|
||
|
/**
|
||
|
* A DOM element, a `ref` to an element, or function that returns either. The Modal is appended to it's `container` element.
|
||
|
*
|
||
|
* For the sake of assistive technologies, the container should usually be the document body, so that the rest of the
|
||
|
* page content can be placed behind a virtual backdrop as well as a visual one.
|
||
|
*/
|
||
|
container: PropTypes.any,
|
||
|
|
||
|
/**
|
||
|
* A callback fired when the Modal is opening.
|
||
|
*/
|
||
|
onShow: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* A callback fired when either the backdrop is clicked, or the escape key is pressed.
|
||
|
*
|
||
|
* The `onHide` callback only signals intent from the Modal,
|
||
|
* you must actually set the `show` prop to `false` for the Modal to close.
|
||
|
*/
|
||
|
onHide: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* Include a backdrop component.
|
||
|
*/
|
||
|
backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
|
||
|
|
||
|
/**
|
||
|
* A function that returns the dialog component. Useful for custom
|
||
|
* rendering. **Note:** the component should make sure to apply the provided ref.
|
||
|
*
|
||
|
* ```js static
|
||
|
* renderDialog={props => <MyDialog {...props} />}
|
||
|
* ```
|
||
|
*/
|
||
|
renderDialog: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* A function that returns a backdrop component. Useful for custom
|
||
|
* backdrop rendering.
|
||
|
*
|
||
|
* ```js
|
||
|
* renderBackdrop={props => <MyBackdrop {...props} />}
|
||
|
* ```
|
||
|
*/
|
||
|
renderBackdrop: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* A callback fired when the escape key, if specified in `keyboard`, is pressed.
|
||
|
*
|
||
|
* If preventDefault() is called on the keyboard event, closing the modal will be cancelled.
|
||
|
*/
|
||
|
onEscapeKeyDown: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* A callback fired when the backdrop, if specified, is clicked.
|
||
|
*/
|
||
|
onBackdropClick: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* A css class or set of classes applied to the modal container when the modal is open,
|
||
|
* and removed when it is closed.
|
||
|
*/
|
||
|
containerClassName: PropTypes.string,
|
||
|
|
||
|
/**
|
||
|
* Close the modal when escape key is pressed
|
||
|
*/
|
||
|
keyboard: PropTypes.bool,
|
||
|
|
||
|
/**
|
||
|
* A `react-transition-group@2.0.0` `<Transition/>` component used
|
||
|
* to control animations for the dialog component.
|
||
|
*/
|
||
|
transition: PropTypes.elementType,
|
||
|
|
||
|
/**
|
||
|
* A `react-transition-group@2.0.0` `<Transition/>` component used
|
||
|
* to control animations for the backdrop components.
|
||
|
*/
|
||
|
backdropTransition: PropTypes.elementType,
|
||
|
|
||
|
/**
|
||
|
* When `true` The modal will automatically shift focus to itself when it opens, and
|
||
|
* replace it to the last focused element when it closes. This also
|
||
|
* works correctly with any Modal children that have the `autoFocus` prop.
|
||
|
*
|
||
|
* Generally this should never be set to `false` as it makes the Modal less
|
||
|
* accessible to assistive technologies, like screen readers.
|
||
|
*/
|
||
|
autoFocus: PropTypes.bool,
|
||
|
|
||
|
/**
|
||
|
* When `true` The modal will prevent focus from leaving the Modal while open.
|
||
|
*
|
||
|
* Generally this should never be set to `false` as it makes the Modal less
|
||
|
* accessible to assistive technologies, like screen readers.
|
||
|
*/
|
||
|
enforceFocus: PropTypes.bool,
|
||
|
|
||
|
/**
|
||
|
* When `true` The modal will restore focus to previously focused element once
|
||
|
* modal is hidden
|
||
|
*/
|
||
|
restoreFocus: PropTypes.bool,
|
||
|
|
||
|
/**
|
||
|
* Options passed to focus function when `restoreFocus` is set to `true`
|
||
|
*
|
||
|
* @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Parameters
|
||
|
*/
|
||
|
restoreFocusOptions: PropTypes.shape({
|
||
|
preventScroll: PropTypes.bool
|
||
|
}),
|
||
|
|
||
|
/**
|
||
|
* Callback fired before the Modal transitions in
|
||
|
*/
|
||
|
onEnter: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* Callback fired as the Modal begins to transition in
|
||
|
*/
|
||
|
onEntering: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* Callback fired after the Modal finishes transitioning in
|
||
|
*/
|
||
|
onEntered: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* Callback fired right before the Modal transitions out
|
||
|
*/
|
||
|
onExit: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* Callback fired as the Modal begins to transition out
|
||
|
*/
|
||
|
onExiting: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* Callback fired after the Modal finishes transitioning out
|
||
|
*/
|
||
|
onExited: PropTypes.func,
|
||
|
|
||
|
/**
|
||
|
* A ModalManager instance used to track and manage the state of open
|
||
|
* Modals. Useful when customizing how modals interact within a container
|
||
|
*/
|
||
|
manager: PropTypes.instanceOf(ModalManager)
|
||
|
};
|
||
|
Modal.displayName = 'Modal';
|
||
|
Modal.propTypes = propTypes;
|
||
|
export default Object.assign(Modal, {
|
||
|
Manager: ModalManager
|
||
|
});
|