UNPKG

simple-react-lightbox

Version:
1,690 lines (1,430 loc) 171 kB
import React, { useReducer, useContext, useRef, useState, useEffect, Children, isValidElement, cloneElement, createContext, createElement, Fragment, forwardRef, Component, useCallback } from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _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; }; return _extends.apply(this, arguments); } 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 _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _newArrowCheck(innerThis, boundThis) { if (innerThis !== boundThis) { throw new TypeError("Cannot instantiate an arrow function"); } } function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } var _this = undefined; var isEqual = require("lodash/isEqual"); var SRLCtx = React.createContext(); var SRLContextComponent = function SRLContextComponent(props) { var _this2 = this; _newArrowCheck(this, _this); var initialState = { isOpened: false, images: [], selectedImage: { source: "", caption: "", id: "", width: "", height: "" } }; function reducer(state, action) { switch (action.type) { case "handleLightbox": return _objectSpread2({}, state, { isOpened: true, selectedImage: { source: action.payload.img, caption: action.payload.alt, id: action.payload.id, width: action.payload.width, height: action.payload.height } }); case "grabImages": return _objectSpread2({}, state, { images: action.images // IE 11 -_- }); case "handleCloseLightbox": return _objectSpread2({}, state, { isOpened: false }); default: return state; } } var _useReducer = useReducer(reducer, initialState), _useReducer2 = _slicedToArray(_useReducer, 2), state = _useReducer2[0], dispatch = _useReducer2[1]; var handleLightbox = function handleLightbox(img, alt, id, width, height) { _newArrowCheck(this, _this2); var payload = { img: img, alt: alt, id: id, width: width, height: height }; if (!state.isOpened) { if (!isEqual(state.selectedImage, payload)) { dispatch({ type: "handleLightbox", payload: payload }); } } }.bind(this); var grabImages = function grabImages(images) { _newArrowCheck(this, _this2); // IT'S CRUCIAL TO ADD THESE CONDITIONALS CHECK OR THE DISPATCH WILL RUN INFINITELY /* First: we compare the "prev state" with the new state from the context to see if the images is not an empty array Second: we check if the two objects are different. In this case it means that there are multiple galleries and we want to grab the new images */ if (!state.isOpened) { if (state.images.length < images.length || !isEqual(state.images, images)) { dispatch({ type: "grabImages", images: images }); } } }.bind(this); function handleCloseLightbox() { if (state.isOpened) { dispatch({ type: "handleCloseLightbox" }); } } return React.createElement(SRLCtx.Provider, { value: _objectSpread2({ handleLightbox: handleLightbox, grabImages: grabImages, handleCloseLightbox: handleCloseLightbox }, state, {}, props) }, props.children); }.bind(undefined); SRLContextComponent.propTypes = { children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired }; var _this$1 = undefined; var SRLWrapper = function SRLWrapper(props) { var _this2 = this; _newArrowCheck(this, _this$1); // Imports the context var context = useContext(SRLCtx); // Sets a new Ref which will be used to target the div with the images var imagesContainer = useRef(null); // Creates a state to check if the images are set var _useState = useState(false), _useState2 = _slicedToArray(_useState, 2), isImageSet = _useState2[0], setIsImageSet = _useState2[1]; // Grabs the images and set them using setImages useEffect(function () { var _this3 = this; _newArrowCheck(this, _this2); // Gets an HTMLCollection which we need to change to a normal array. var collectedImages = imagesContainer.current.getElementsByTagName("img"); // Gets all the links with the "SRL" attribute. This is in case the user wants to create a "custom" gallery. var collectedLinks = imagesContainer.current.querySelectorAll("a[data-attribute='SRL']"); var imagesArray = Array.prototype.slice.call(collectedImages); var collectedLinksArray = Array.prototype.slice.call(collectedLinks); // Checks if collectedImages is not empty (which means there were no images) // Checks if collectedLinks is empty which means that the user is just using images if (collectedImages.length > 0 && collectedLinks.length === 0) { if (!isImageSet) { var images = []; // Iterates through each image imagesArray.map(function (i, index) { var _this4 = this; _newArrowCheck(this, _this3); // Assigns a new ID attribute to the image i.id = "img".concat(index); // Creates a temporary image object var image = { src: i.src, // Grabs the "src" attribute from the image alt: i.alt, // Grabs the "alt" attribute from the image, id: i.id, // Grabs the newly created "id" attribute from the image width: i.width, // Grabs the "width" attribute from the image height: i.height // Grabs the "height" attribute from the image }; images.push(image); // Adds an event listerner that will trigger the function to open the lightbox (passed using the Context) i.addEventListener("click", function (e) { _newArrowCheck(this, _this4); context.handleLightbox(e.target.src, e.target.alt, e.target.id, e.target.width, e.target.height); }.bind(this)); // Avoid ifinite re-rendering by changing this value to TRUE setIsImageSet(true); }.bind(this)); // Dispatch the action to set the images from the context context.grabImages(images); } } if (collectedLinks.length > 0) { if (collectedImages.length !== collectedLinks.length) { // Throws a warning if the number of links is not equal to the number of images console.warn("Hey. You have ".concat(collectedLinks.length, " links and ").concat(collectedImages.length, ". You likely forgot to add the data-attribute=\"SRL\" to one or more than one of your images!")); } if (!isImageSet) { var _images = []; // Iterates through each link collectedLinksArray.map(function (a, index) { var _this5 = this; _newArrowCheck(this, _this3); // Assigns a new ID attribute to the image (children of the link) a.children[0].id = "img".concat(index); // Creates a temporary image object var image = { src: a.href, // Grabs the "href" attribute from the link (not from the image) alt: a.children[0].alt, // Grabs the "alt" attribute from the image id: a.children[0].id, // Grabs the newly created "id" attribute from the image width: a.children[0].width, // Grabs the "width" attribute from the image height: a.children[0].height // Grabs the "height" attribute from the image }; _images.push(image); // Adds an event listerner that will trigger the function to open the lightbox (passed using the Context) a.addEventListener("click", function (e) { _newArrowCheck(this, _this5); e.preventDefault(); context.handleLightbox( /* This is slightly different from the one above as we want to grab the "href" attribute of the link, not the "src" attribute of the image */ e.target.parentNode.href, e.target.alt, e.target.id, e.target.width, e.target.height); }.bind(this)); // Avoid ifinite re-rendering by changing this value to TRUE setIsImageSet(true); }.bind(this)); // Dispatch the action to set the images from the context context.grabImages(_images); } } }.bind(this), [context, isImageSet, props]); return React.createElement("div", { ref: imagesContainer }, props.children); }.bind(undefined); SRLWrapper.propTypes = { children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired }; function _extends$1() { _extends$1 = 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; }; return _extends$1.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } function hasClass(element, className) { if (element.classList) return !!className && element.classList.contains(className); return (" " + (element.className.baseVal || element.className) + " ").indexOf(" " + className + " ") !== -1; } function addClass(element, className) { if (element.classList) element.classList.add(className);else if (!hasClass(element, className)) if (typeof element.className === 'string') element.className = element.className + " " + className;else element.setAttribute('class', (element.className && element.className.baseVal || '') + " " + className); } function replaceClassName(origClass, classToRemove) { return origClass.replace(new RegExp("(^|\\s)" + classToRemove + "(?:\\s|$)", 'g'), '$1').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, ''); } function removeClass(element, className) { if (element.classList) { element.classList.remove(className); } else if (typeof element.className === 'string') { element.className = replaceClassName(element.className, className); } else { element.setAttribute('class', replaceClassName(element.className && element.className.baseVal || '', className)); } } var config = { disabled: false }; var timeoutsShape = process.env.NODE_ENV !== 'production' ? PropTypes.oneOfType([PropTypes.number, PropTypes.shape({ enter: PropTypes.number, exit: PropTypes.number, appear: PropTypes.number }).isRequired]) : null; var classNamesShape = process.env.NODE_ENV !== 'production' ? PropTypes.oneOfType([PropTypes.string, PropTypes.shape({ enter: PropTypes.string, exit: PropTypes.string, active: PropTypes.string }), PropTypes.shape({ enter: PropTypes.string, enterDone: PropTypes.string, enterActive: PropTypes.string, exit: PropTypes.string, exitDone: PropTypes.string, exitActive: PropTypes.string })]) : null; var TransitionGroupContext = React.createContext(null); var UNMOUNTED = 'unmounted'; var EXITED = 'exited'; var ENTERING = 'entering'; var ENTERED = 'entered'; var EXITING = 'exiting'; /** * The Transition component lets you describe a transition from one component * state to another _over time_ with a simple declarative API. Most commonly * it's used to animate the mounting and unmounting of a component, but can also * be used to describe in-place transition states as well. * * --- * * **Note**: `Transition` is a platform-agnostic base component. If you're using * transitions in CSS, you'll probably want to use * [`CSSTransition`](https://reactcommunity.org/react-transition-group/css-transition) * instead. It inherits all the features of `Transition`, but contains * additional features necessary to play nice with CSS transitions (hence the * name of the component). * * --- * * By default the `Transition` component does not alter the behavior of the * component it renders, it only tracks "enter" and "exit" states for the * components. It's up to you to give meaning and effect to those states. For * example we can add styles to a component when it enters or exits: * * ```jsx * import { Transition } from 'react-transition-group'; * * const duration = 300; * * const defaultStyle = { * transition: `opacity ${duration}ms ease-in-out`, * opacity: 0, * } * * const transitionStyles = { * entering: { opacity: 1 }, * entered: { opacity: 1 }, * exiting: { opacity: 0 }, * exited: { opacity: 0 }, * }; * * const Fade = ({ in: inProp }) => ( * <Transition in={inProp} timeout={duration}> * {state => ( * <div style={{ * ...defaultStyle, * ...transitionStyles[state] * }}> * I'm a fade Transition! * </div> * )} * </Transition> * ); * ``` * * There are 4 main states a Transition can be in: * - `'entering'` * - `'entered'` * - `'exiting'` * - `'exited'` * * Transition state is toggled via the `in` prop. When `true` the component * begins the "Enter" stage. During this stage, the component will shift from * its current transition state, to `'entering'` for the duration of the * transition and then to the `'entered'` stage once it's complete. Let's take * the following example (we'll use the * [useState](https://reactjs.org/docs/hooks-reference.html#usestate) hook): * * ```jsx * function App() { * const [inProp, setInProp] = useState(false); * return ( * <div> * <Transition in={inProp} timeout={500}> * {state => ( * // ... * )} * </Transition> * <button onClick={() => setInProp(true)}> * Click to Enter * </button> * </div> * ); * } * ``` * * When the button is clicked the component will shift to the `'entering'` state * and stay there for 500ms (the value of `timeout`) before it finally switches * to `'entered'`. * * When `in` is `false` the same thing happens except the state moves from * `'exiting'` to `'exited'`. */ var Transition = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(Transition, _React$Component); function Transition(props, context) { var _this; _this = _React$Component.call(this, props, context) || this; var parentGroup = context; // In the context of a TransitionGroup all enters are really appears var appear = parentGroup && !parentGroup.isMounting ? props.enter : props.appear; var initialStatus; _this.appearStatus = null; if (props.in) { if (appear) { initialStatus = EXITED; _this.appearStatus = ENTERING; } else { initialStatus = ENTERED; } } else { if (props.unmountOnExit || props.mountOnEnter) { initialStatus = UNMOUNTED; } else { initialStatus = EXITED; } } _this.state = { status: initialStatus }; _this.nextCallback = null; return _this; } Transition.getDerivedStateFromProps = function getDerivedStateFromProps(_ref, prevState) { var nextIn = _ref.in; if (nextIn && prevState.status === UNMOUNTED) { return { status: EXITED }; } return null; }; // getSnapshotBeforeUpdate(prevProps) { // let nextStatus = null // if (prevProps !== this.props) { // const { status } = this.state // if (this.props.in) { // if (status !== ENTERING && status !== ENTERED) { // nextStatus = ENTERING // } // } else { // if (status === ENTERING || status === ENTERED) { // nextStatus = EXITING // } // } // } // return { nextStatus } // } var _proto = Transition.prototype; _proto.componentDidMount = function componentDidMount() { this.updateStatus(true, this.appearStatus); }; _proto.componentDidUpdate = function componentDidUpdate(prevProps) { var nextStatus = null; if (prevProps !== this.props) { var status = this.state.status; if (this.props.in) { if (status !== ENTERING && status !== ENTERED) { nextStatus = ENTERING; } } else { if (status === ENTERING || status === ENTERED) { nextStatus = EXITING; } } } this.updateStatus(false, nextStatus); }; _proto.componentWillUnmount = function componentWillUnmount() { this.cancelNextCallback(); }; _proto.getTimeouts = function getTimeouts() { var timeout = this.props.timeout; var exit, enter, appear; exit = enter = appear = timeout; if (timeout != null && typeof timeout !== 'number') { exit = timeout.exit; enter = timeout.enter; // TODO: remove fallback for next major appear = timeout.appear !== undefined ? timeout.appear : enter; } return { exit: exit, enter: enter, appear: appear }; }; _proto.updateStatus = function updateStatus(mounting, nextStatus) { if (mounting === void 0) { mounting = false; } if (nextStatus !== null) { // nextStatus will always be ENTERING or EXITING. this.cancelNextCallback(); var node = ReactDOM.findDOMNode(this); if (nextStatus === ENTERING) { this.performEnter(node, mounting); } else { this.performExit(node); } } else if (this.props.unmountOnExit && this.state.status === EXITED) { this.setState({ status: UNMOUNTED }); } }; _proto.performEnter = function performEnter(node, mounting) { var _this2 = this; var enter = this.props.enter; var appearing = this.context ? this.context.isMounting : mounting; var timeouts = this.getTimeouts(); var enterTimeout = appearing ? timeouts.appear : timeouts.enter; // no enter animation skip right to ENTERED // if we are mounting and running this it means appear _must_ be set if (!mounting && !enter || config.disabled) { this.safeSetState({ status: ENTERED }, function () { _this2.props.onEntered(node); }); return; } this.props.onEnter(node, appearing); this.safeSetState({ status: ENTERING }, function () { _this2.props.onEntering(node, appearing); _this2.onTransitionEnd(node, enterTimeout, function () { _this2.safeSetState({ status: ENTERED }, function () { _this2.props.onEntered(node, appearing); }); }); }); }; _proto.performExit = function performExit(node) { var _this3 = this; var exit = this.props.exit; var timeouts = this.getTimeouts(); // no exit animation skip right to EXITED if (!exit || config.disabled) { this.safeSetState({ status: EXITED }, function () { _this3.props.onExited(node); }); return; } this.props.onExit(node); this.safeSetState({ status: EXITING }, function () { _this3.props.onExiting(node); _this3.onTransitionEnd(node, timeouts.exit, function () { _this3.safeSetState({ status: EXITED }, function () { _this3.props.onExited(node); }); }); }); }; _proto.cancelNextCallback = function cancelNextCallback() { if (this.nextCallback !== null) { this.nextCallback.cancel(); this.nextCallback = null; } }; _proto.safeSetState = function safeSetState(nextState, callback) { // This shouldn't be necessary, but there are weird race conditions with // setState callbacks and unmounting in testing, so always make sure that // we can cancel any pending setState callbacks after we unmount. callback = this.setNextCallback(callback); this.setState(nextState, callback); }; _proto.setNextCallback = function setNextCallback(callback) { var _this4 = this; var active = true; this.nextCallback = function (event) { if (active) { active = false; _this4.nextCallback = null; callback(event); } }; this.nextCallback.cancel = function () { active = false; }; return this.nextCallback; }; _proto.onTransitionEnd = function onTransitionEnd(node, timeout, handler) { this.setNextCallback(handler); var doesNotHaveTimeoutOrListener = timeout == null && !this.props.addEndListener; if (!node || doesNotHaveTimeoutOrListener) { setTimeout(this.nextCallback, 0); return; } if (this.props.addEndListener) { this.props.addEndListener(node, this.nextCallback); } if (timeout != null) { setTimeout(this.nextCallback, timeout); } }; _proto.render = function render() { var status = this.state.status; if (status === UNMOUNTED) { return null; } var _this$props = this.props, children = _this$props.children, childProps = _objectWithoutPropertiesLoose(_this$props, ["children"]); // filter props for Transtition delete childProps.in; delete childProps.mountOnEnter; delete childProps.unmountOnExit; delete childProps.appear; delete childProps.enter; delete childProps.exit; delete childProps.timeout; delete childProps.addEndListener; delete childProps.onEnter; delete childProps.onEntering; delete childProps.onEntered; delete childProps.onExit; delete childProps.onExiting; delete childProps.onExited; if (typeof children === 'function') { // allows for nested Transitions return React.createElement(TransitionGroupContext.Provider, { value: null }, children(status, childProps)); } var child = React.Children.only(children); return (// allows for nested Transitions React.createElement(TransitionGroupContext.Provider, { value: null }, React.cloneElement(child, childProps)) ); }; return Transition; }(React.Component); Transition.contextType = TransitionGroupContext; Transition.propTypes = process.env.NODE_ENV !== "production" ? { /** * A `function` child can be used instead of a React element. This function is * called with the current transition status (`'entering'`, `'entered'`, * `'exiting'`, `'exited'`), which can be used to apply context * specific props to a component. * * ```jsx * <Transition in={this.state.in} timeout={150}> * {state => ( * <MyComponent className={`fade fade-${state}`} /> * )} * </Transition> * ``` */ children: PropTypes.oneOfType([PropTypes.func.isRequired, PropTypes.element.isRequired]).isRequired, /** * Show the component; triggers the enter or exit states */ in: PropTypes.bool, /** * By default the child component is mounted immediately along with * the parent `Transition` component. If you want to "lazy mount" the component on the * first `in={true}` you can set `mountOnEnter`. After the first enter transition the component will stay * mounted, even on "exited", unless you also specify `unmountOnExit`. */ mountOnEnter: PropTypes.bool, /** * By default the child component stays mounted after it reaches the `'exited'` state. * Set `unmountOnExit` if you'd prefer to unmount the component after it finishes exiting. */ unmountOnExit: PropTypes.bool, /** * Normally a component is not transitioned if it is shown when the * `<Transition>` component mounts. If you want to transition on the first * mount set `appear` to `true`, and the component will transition in as soon * as the `<Transition>` mounts. * * > **Note**: there are no special appear states like `appearing`/`appeared`, this prop * > only adds an additional enter transition. However, in the * > `<CSSTransition>` component that first enter transition does result in * > additional `.appear-*` classes, that way you can choose to style it * > differently. */ appear: PropTypes.bool, /** * Enable or disable enter transitions. */ enter: PropTypes.bool, /** * Enable or disable exit transitions. */ exit: PropTypes.bool, /** * The duration of the transition, in milliseconds. * Required unless `addEndListener` is provided. * * You may specify a single timeout for all transitions: * * ```jsx * timeout={500} * ``` * * or individually: * * ```jsx * timeout={{ * appear: 500, * enter: 300, * exit: 500, * }} * ``` * * - `appear` defaults to the value of `enter` * - `enter` defaults to `0` * - `exit` defaults to `0` * * @type {number | { enter?: number, exit?: number, appear?: number }} */ timeout: function timeout(props) { var pt = timeoutsShape; if (!props.addEndListener) pt = pt.isRequired; for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } return pt.apply(void 0, [props].concat(args)); }, /** * Add a custom transition end trigger. Called with the transitioning * DOM node and a `done` callback. Allows for more fine grained transition end * logic. **Note:** Timeouts are still used as a fallback if provided. * * ```jsx * addEndListener={(node, done) => { * // use the css transitionend event to mark the finish of a transition * node.addEventListener('transitionend', done, false); * }} * ``` */ addEndListener: PropTypes.func, /** * Callback fired before the "entering" status is applied. An extra parameter * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount * * @type Function(node: HtmlElement, isAppearing: bool) -> void */ onEnter: PropTypes.func, /** * Callback fired after the "entering" status is applied. An extra parameter * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount * * @type Function(node: HtmlElement, isAppearing: bool) */ onEntering: PropTypes.func, /** * Callback fired after the "entered" status is applied. An extra parameter * `isAppearing` is supplied to indicate if the enter stage is occurring on the initial mount * * @type Function(node: HtmlElement, isAppearing: bool) -> void */ onEntered: PropTypes.func, /** * Callback fired before the "exiting" status is applied. * * @type Function(node: HtmlElement) -> void */ onExit: PropTypes.func, /** * Callback fired after the "exiting" status is applied. * * @type Function(node: HtmlElement) -> void */ onExiting: PropTypes.func, /** * Callback fired after the "exited" status is applied. * * @type Function(node: HtmlElement) -> void */ onExited: PropTypes.func // Name the function so it is clearer in the documentation } : {}; function noop() {} Transition.defaultProps = { in: false, mountOnEnter: false, unmountOnExit: false, appear: false, enter: true, exit: true, onEnter: noop, onEntering: noop, onEntered: noop, onExit: noop, onExiting: noop, onExited: noop }; Transition.UNMOUNTED = 0; Transition.EXITED = 1; Transition.ENTERING = 2; Transition.ENTERED = 3; Transition.EXITING = 4; var _addClass = function addClass$1(node, classes) { return node && classes && classes.split(' ').forEach(function (c) { return addClass(node, c); }); }; var removeClass$1 = function removeClass$1(node, classes) { return node && classes && classes.split(' ').forEach(function (c) { return removeClass(node, c); }); }; /** * A transition component inspired by the excellent * [ng-animate](http://www.nganimate.org/) library, you should use it if you're * using CSS transitions or animations. It's built upon the * [`Transition`](https://reactcommunity.org/react-transition-group/transition) * component, so it inherits all of its props. * * `CSSTransition` applies a pair of class names during the `appear`, `enter`, * and `exit` states of the transition. The first class is applied and then a * second `*-active` class in order to activate the CSS transition. After the * transition, matching `*-done` class names are applied to persist the * transition state. * * ```jsx * function App() { * const [inProp, setInProp] = useState(false); * return ( * <div> * <CSSTransition in={inProp} timeout={200} classNames="my-node"> * <div> * {"I'll receive my-node-* classes"} * </div> * </CSSTransition> * <button type="button" onClick={() => setInProp(true)}> * Click to Enter * </button> * </div> * ); * } * ``` * * When the `in` prop is set to `true`, the child component will first receive * the class `example-enter`, then the `example-enter-active` will be added in * the next tick. `CSSTransition` [forces a * reflow](https://github.com/reactjs/react-transition-group/blob/5007303e729a74be66a21c3e2205e4916821524b/src/CSSTransition.js#L208-L215) * between before adding the `example-enter-active`. This is an important trick * because it allows us to transition between `example-enter` and * `example-enter-active` even though they were added immediately one after * another. Most notably, this is what makes it possible for us to animate * _appearance_. * * ```css * .my-node-enter { * opacity: 0; * } * .my-node-enter-active { * opacity: 1; * transition: opacity 200ms; * } * .my-node-exit { * opacity: 1; * } * .my-node-exit-active { * opacity: 0; * transition: opacity 200ms; * } * ``` * * `*-active` classes represent which styles you want to animate **to**. * * **Note**: If you're using the * [`appear`](http://reactcommunity.org/react-transition-group/transition#Transition-prop-appear) * prop, make sure to define styles for `.appear-*` classes as well. */ var CSSTransition = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(CSSTransition, _React$Component); function CSSTransition() { var _this; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this; _this.appliedClasses = { appear: {}, enter: {}, exit: {} }; _this.onEnter = function (node, appearing) { _this.removeClasses(node, 'exit'); _this.addClass(node, appearing ? 'appear' : 'enter', 'base'); if (_this.props.onEnter) { _this.props.onEnter(node, appearing); } }; _this.onEntering = function (node, appearing) { var type = appearing ? 'appear' : 'enter'; _this.addClass(node, type, 'active'); if (_this.props.onEntering) { _this.props.onEntering(node, appearing); } }; _this.onEntered = function (node, appearing) { var type = appearing ? 'appear' : 'enter'; _this.removeClasses(node, type); _this.addClass(node, type, 'done'); if (_this.props.onEntered) { _this.props.onEntered(node, appearing); } }; _this.onExit = function (node) { _this.removeClasses(node, 'appear'); _this.removeClasses(node, 'enter'); _this.addClass(node, 'exit', 'base'); if (_this.props.onExit) { _this.props.onExit(node); } }; _this.onExiting = function (node) { _this.addClass(node, 'exit', 'active'); if (_this.props.onExiting) { _this.props.onExiting(node); } }; _this.onExited = function (node) { _this.removeClasses(node, 'exit'); _this.addClass(node, 'exit', 'done'); if (_this.props.onExited) { _this.props.onExited(node); } }; _this.getClassNames = function (type) { var classNames = _this.props.classNames; var isStringClassNames = typeof classNames === 'string'; var prefix = isStringClassNames && classNames ? classNames + "-" : ''; var baseClassName = isStringClassNames ? "" + prefix + type : classNames[type]; var activeClassName = isStringClassNames ? baseClassName + "-active" : classNames[type + "Active"]; var doneClassName = isStringClassNames ? baseClassName + "-done" : classNames[type + "Done"]; return { baseClassName: baseClassName, activeClassName: activeClassName, doneClassName: doneClassName }; }; return _this; } var _proto = CSSTransition.prototype; _proto.addClass = function addClass(node, type, phase) { var className = this.getClassNames(type)[phase + "ClassName"]; if (type === 'appear' && phase === 'done') { className += " " + this.getClassNames('enter').doneClassName; } // This is for to force a repaint, // which is necessary in order to transition styles when adding a class name. if (phase === 'active') { /* eslint-disable no-unused-expressions */ node && node.scrollTop; } this.appliedClasses[type][phase] = className; _addClass(node, className); }; _proto.removeClasses = function removeClasses(node, type) { var _this$appliedClasses$ = this.appliedClasses[type], baseClassName = _this$appliedClasses$.base, activeClassName = _this$appliedClasses$.active, doneClassName = _this$appliedClasses$.done; this.appliedClasses[type] = {}; if (baseClassName) { removeClass$1(node, baseClassName); } if (activeClassName) { removeClass$1(node, activeClassName); } if (doneClassName) { removeClass$1(node, doneClassName); } }; _proto.render = function render() { var _this$props = this.props, _ = _this$props.classNames, props = _objectWithoutPropertiesLoose(_this$props, ["classNames"]); return React.createElement(Transition, _extends$1({}, props, { onEnter: this.onEnter, onEntered: this.onEntered, onEntering: this.onEntering, onExit: this.onExit, onExiting: this.onExiting, onExited: this.onExited })); }; return CSSTransition; }(React.Component); CSSTransition.defaultProps = { classNames: '' }; CSSTransition.propTypes = process.env.NODE_ENV !== "production" ? _extends$1({}, Transition.propTypes, { /** * The animation classNames applied to the component as it appears, enters, * exits or has finished the transition. A single name can be provided and it * will be suffixed for each stage: e.g. * * `classNames="fade"` applies `fade-appear`, `fade-appear-active`, * `fade-appear-done`, `fade-enter`, `fade-enter-active`, `fade-enter-done`, * `fade-exit`, `fade-exit-active`, and `fade-exit-done`. * * **Note**: `fade-appear-done` and `fade-enter-done` will _both_ be applied. * This allows you to define different behavior for when appearing is done and * when regular entering is done, using selectors like * `.fade-enter-done:not(.fade-appear-done)`. For example, you could apply an * epic entrance animation when element first appears in the DOM using * [Animate.css](https://daneden.github.io/animate.css/). Otherwise you can * simply use `fade-enter-done` for defining both cases. * * Each individual classNames can also be specified independently like: * * ```js * classNames={{ * appear: 'my-appear', * appearActive: 'my-active-appear', * appearDone: 'my-done-appear', * enter: 'my-enter', * enterActive: 'my-active-enter', * enterDone: 'my-done-enter', * exit: 'my-exit', * exitActive: 'my-active-exit', * exitDone: 'my-done-exit', * }} * ``` * * If you want to set these classes using CSS Modules: * * ```js * import styles from './styles.css'; * ``` * * you might want to use camelCase in your CSS file, that way could simply * spread them instead of listing them one by one: * * ```js * classNames={{ ...styles }} * ``` * * @type {string | { * appear?: string, * appearActive?: string, * appearDone?: string, * enter?: string, * enterActive?: string, * enterDone?: string, * exit?: string, * exitActive?: string, * exitDone?: string, * }} */ classNames: classNamesShape, /** * A `<Transition>` callback fired immediately after the 'enter' or 'appear' class is * applied. * * @type Function(node: HtmlElement, isAppearing: bool) */ onEnter: PropTypes.func, /** * A `<Transition>` callback fired immediately after the 'enter-active' or * 'appear-active' class is applied. * * @type Function(node: HtmlElement, isAppearing: bool) */ onEntering: PropTypes.func, /** * A `<Transition>` callback fired immediately after the 'enter' or * 'appear' classes are **removed** and the `done` class is added to the DOM node. * * @type Function(node: HtmlElement, isAppearing: bool) */ onEntered: PropTypes.func, /** * A `<Transition>` callback fired immediately after the 'exit' class is * applied. * * @type Function(node: HtmlElement) */ onExit: PropTypes.func, /** * A `<Transition>` callback fired immediately after the 'exit-active' is applied. * * @type Function(node: HtmlElement) */ onExiting: PropTypes.func, /** * A `<Transition>` callback fired immediately after the 'exit' classes * are **removed** and the `exit-done` class is added to the DOM node. * * @type Function(node: HtmlElement) */ onExited: PropTypes.func }) : {}; function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } /** * Given `this.props.children`, return an object mapping key to child. * * @param {*} children `this.props.children` * @return {object} Mapping of key to child */ function getChildMapping(children, mapFn) { var mapper = function mapper(child) { return mapFn && isValidElement(child) ? mapFn(child) : child; }; var result = Object.create(null); if (children) Children.map(children, function (c) { return c; }).forEach(function (child) { // run the map function here instead so that the key is the computed one result[child.key] = mapper(child); }); return result; } /** * When you're adding or removing children some may be added or removed in the * same render pass. We want to show *both* since we want to simultaneously * animate elements in and out. This function takes a previous set of keys * and a new set of keys and merges them with its best guess of the correct * ordering. In the future we may expose some of the utilities in * ReactMultiChild to make this easy, but for now React itself does not * directly have this concept of the union of prevChildren and nextChildren * so we implement it here. * * @param {object} prev prev children as returned from * `ReactTransitionChildMapping.getChildMapping()`. * @param {object} next next children as returned from * `ReactTransitionChildMapping.getChildMapping()`. * @return {object} a key set that contains all keys in `prev` and all keys * in `next` in a reasonable order. */ function mergeChildMappings(prev, next) { prev = prev || {}; next = next || {}; function getValueForKey(key) { return key in next ? next[key] : prev[key]; } // For each key of `next`, the list of keys to insert before that key in // the combined list var nextKeysPending = Object.create(null); var pendingKeys = []; for (var prevKey in prev) { if (prevKey in next) { if (pendingKeys.length) { nextKeysPending[prevKey] = pendingKeys; pendingKeys = []; } } else { pendingKeys.push(prevKey); } } var i; var childMapping = {}; for (var nextKey in next) { if (nextKeysPending[nextKey]) { for (i = 0; i < nextKeysPending[nextKey].length; i++) { var pendingNextKey = nextKeysPending[nextKey][i]; childMapping[nextKeysPending[nextKey][i]] = getValueForKey(pendingNextKey); } } childMapping[nextKey] = getValueForKey(nextKey); } // Finally, add the keys which didn't appear before any key in `next` for (i = 0; i < pendingKeys.length; i++) { childMapping[pendingKeys[i]] = getValueForKey(pendingKeys[i]); } return childMapping; } function getProp(child, prop, props) { return props[prop] != null ? props[prop] : child.props[prop]; } function getInitialChildMapping(props, onExited) { return getChildMapping(props.children, function (child) { return cloneElement(child, { onExited: onExited.bind(null, child), in: true, appear: getProp(child, 'appear', props), enter: getProp(child, 'enter', props), exit: getProp(child, 'exit', props) }); }); } function getNextChildMapping(nextProps, prevChildMapping, onExited) { var nextChildMapping = getChildMapping(nextProps.children); var children = mergeChildMappings(prevChildMapping, nextChildMapping); Object.keys(children).forEach(function (key) { var child = children[key]; if (!isValidElement(child)) return; var hasPrev = key in prevChildMapping; var hasNext = key in nextChildMapping; var prevChild = prevChildMapping[key]; var isLeaving = isValidElement(prevChild) && !prevChild.props.in; // item is new (entering) if (hasNext && (!hasPrev || isLeaving)) { // console.log('entering', key) children[key] = cloneElement(child, { onExited: onExited.bind(null, child), in: true, exit: getProp(child, 'exit', nextProps), enter: getProp(child, 'enter', nextProps) }); } else if (!hasNext && hasPrev && !isLeaving) { // item is old (exiting) // console.log('leaving', key) children[key] = cloneElement(child, { in: false }); } else if (hasNext && hasPrev && isValidElement(prevChild)) { // item hasn't changed transition states // copy over the last transition props; // console.log('unchanged', key) children[key] = cloneElement(child, { onExited: onExited.bind(null, child), in: prevChild.props.in, exit: getProp(child, 'exit', nextProps), enter: getProp(child, 'enter', nextProps) }); } }); return children; } var values = Object.values || function (obj) { return Object.keys(obj).map(function (k) { return obj[k]; }); }; var defaultProps = { component: 'div', childFactory: function childFactory(child) { return child; } /** * The `<TransitionGroup>` component manages a set of transition components * (`<Transition>` and `<CSSTransition>`) in a list. Like with the transition * components, `<TransitionGroup>` is a state machine for managing the mounting * and unmounting of components over time. * * Consider the example below. As items are removed or added to the TodoList the * `in` prop is toggled automatically by the `<TransitionGroup>`. * * Note that `<TransitionGroup>` does not define any animation behavior! * Exactly _how_ a list item animates is up to the individual transition * component. This means you can mix and match animations across different list * items. */ }; var TransitionGroup = /*#__PURE__*/ function (_React$Component) { _inheritsLoose(TransitionGroup, _React$Component); function TransitionGroup(props, context) { var _this; _this = _React$Component.call(this, props, context) || this; var handleExited = _this.handleExited.bind(_assertThisInitialized(_assertThisInitialized(_this))); // Initial children should all be entering, dependent on appear _this.state = { contextValue: { isMounting: true }, handleExited: handleExited, firstRender: true }; return _this; } var _proto = TransitionGroup.prototype; _proto.componentDidMount = function componentDidMount() { this.mounted = true; this.setState({ contextValue: { isMounting: false } }); }; _proto.componentWillUnmount = function componentWillUnmount() { this.mounted = false; }; TransitionGroup.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, _ref) { var prevChildMapping = _ref.children, handleExited = _ref.handleExited, firstRender = _ref.firstRender; return { children: firstRender ? getInitialChildMapping(nextProps, handleExited) : getNextChildMapping(nextProps, prevChildMapping, handleExited), firstRender: false }; }; _proto.handleExited = function handleExited(child, node) { var currentChildMapping = getChildMapping(this.props.children); if (child.key in currentChildMapping) return; if (child.props.onExited) { child.props.onExited(node); } if (this.mounted) { this.setState(function (state) { var children = _extends$1({}, state.children); delete children[child.key]; return { children: children }; }); } }; _proto.render = function render() { var _this$props = this.props, Component = _this$props.component, childFactory = _this$props.childFactory, props = _objectWithoutPropertiesLoose(_this$props, ["component", "childFactory"]); var contextValue = this.state.contextValue; var children = values(this.state.children).map(childFactory); delete props.appear; delete props.enter; delete props.exit; if (Component === null) { return React.createElement(TransitionGroupContext.Provider, { value: contextValue }, children); } return React.createElement(TransitionGroupContext.Provider, { value: contextValue }, React.createElement(Component, props, children)); }; return TransitionGroup; }(React.Component); TransitionGroup.propTypes = process.env.NODE_ENV !== "production" ? { /** * `<TransitionGroup>` renders a `<div>` by default. You can change this * behavior by providing a `component` prop. * If you use React v16+ and would like to avoid a wrapping `<div>` element * you can pass in `component={null}`. This is useful if the wrapping div * borks your css styles. */ component: PropTypes.any, /** * A set of `<Transition>` components, that are toggled `in` and out as they * leave. the `<TransitionGroup>` will inject specific transition props, so * remember to spread them through if you are wrapping the `<Transition>` as * with our `<Fade>` example. * * While this component is meant for multiple `Transition` or `CSSTransition` * children,