├── .babelrc ├── .prettierrc ├── .gitignore ├── src ├── index.js ├── AnimateGroup.js ├── utils │ └── mergeDiff.js └── Animate.js ├── test ├── enzyme-setup.js ├── Animate.test.js └── AnimateGroup.test.js ├── .flowconfig ├── jest.config.js ├── demo ├── CodeExample.js ├── examples │ ├── Animate-2.js │ ├── Animate-1.js │ └── AnimateGroup.js ├── index.html ├── styles.css └── index.js ├── .eslintrc ├── LICENSE ├── webpack.config.js ├── docs ├── index.html ├── style.css └── docs.js ├── package.json ├── README.md └── flow-typed └── npm └── jest_v23.x.x.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react", "flow", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | .module-cache 4 | *.log* 5 | _nogit 6 | lib -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import Animate from './Animate'; 3 | import AnimateGroup from './AnimateGroup'; 4 | 5 | export { Animate, AnimateGroup } -------------------------------------------------------------------------------- /test/enzyme-setup.js: -------------------------------------------------------------------------------- 1 | const Enzyme = require('enzyme'); 2 | const React16Adapter = require('enzyme-adapter-react-16'); 3 | 4 | Enzyme.configure({ adapter: new React16Adapter() }); 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/dist/.* 3 | .*/lib/.* 4 | 5 | [include] 6 | 7 | [libs] 8 | flow-typed/ 9 | 10 | [lints] 11 | 12 | [options] 13 | module.name_mapper='^react-native$' -> 'react-native-web' 14 | 15 | [strict] 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'react-animate-mount', 3 | rootDir: './', 4 | verbose: true, 5 | transform: { 6 | '^.+\\.js$': 'babel-jest' 7 | }, 8 | setupTestFrameworkScriptFile: '/test/enzyme-setup.js', 9 | testEnvironment: 'jsdom', 10 | testPathIgnorePatterns: ['/node_modules'], 11 | timers: 'fake' 12 | }; 13 | -------------------------------------------------------------------------------- /demo/CodeExample.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Animate } from '../src/Animate'; 4 | 5 | export default class CodeExample extends React.PureComponent { 6 | render() { 7 | const { code, example } = this.props; 8 | return ( 9 |
10 |
{code}
11 | {example} 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 7, 5 | "ecmaFeatures": { 6 | "experimentalObjectRestSpread": true, 7 | "jsx": true 8 | }, 9 | "sourceType": "module" 10 | }, 11 | "extends": ["prettier", "prettier/flowtype", "prettier/react"], 12 | "plugins": ["flowtype", "prettier", "promise", "react", "import"], 13 | "env": { 14 | "es6": true, 15 | "node": true, 16 | "browser": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/examples/Animate-2.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Animate from '../../src/Animate'; 4 | 5 | export default class ExampleAnimate2 extends React.PureComponent { 6 | state = { show: false }; 7 | render() { 8 | return ( 9 |
10 |
11 | Click to toggle animation 12 |
13 |
14 | 15 |
16 |

This component fadeIn and fadeOut

17 |
18 |
19 |
20 |
21 | ); 22 | } 23 | toggleShow = () => { 24 | this.setState({ show: !this.state.show }); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /demo/examples/Animate-1.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Animate from '../../src/Animate' 4 | 5 | export default class ExampleAnimate1 extends React.PureComponent { 6 | state = { show: false }; 7 | render() { 8 | return ( 9 |
10 |
11 | Click to toggle animation 12 |
13 | 14 |
15 |

This component slideUp and slideDown

16 |
17 |
18 |
19 |

20 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the 21 | industry's standard dummy text ever since the 1500s. 22 |

23 |
24 |
25 | ); 26 | } 27 | toggleShow = () => { 28 | this.setState({ show: !this.state.show }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mingrui Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | 7 | const distPath = path.join(__dirname, './docs'); 8 | const docsPath = path.join(__dirname, './demo'); 9 | const srcPath = path.join(__dirname, './src'); 10 | 11 | const rules = [ 12 | { 13 | test: /\.js$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: 'babel-loader' 17 | } 18 | }, 19 | { 20 | test: /\.css$/, 21 | use: ExtractTextPlugin.extract({ 22 | fallback: 'style-loader', 23 | use: ['css-loader'] 24 | }) 25 | } 26 | ]; 27 | 28 | const entry = { 29 | docs: path.join(docsPath, 'index.js') 30 | } 31 | 32 | const plugins = [ 33 | new webpack.HotModuleReplacementPlugin(), 34 | new webpack.NamedModulesPlugin(), 35 | new HtmlWebpackPlugin({ 36 | template: path.join(docsPath, 'index.html'), 37 | path: distPath, 38 | filename: 'index.html', 39 | }), 40 | new ExtractTextPlugin({filename: 'style.css'}) 41 | ] 42 | 43 | module.exports = { 44 | entry, 45 | output: { 46 | path: distPath, 47 | filename: '[name].js', 48 | }, 49 | module: { 50 | rules 51 | }, 52 | plugins 53 | }; 54 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 25 | React Animate Mount 26 | 27 | 28 | 29 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 25 | React Animate Mount 26 | 27 | 28 | 29 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-animate-mount", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "npm run clean && babel src/ -d lib/ && webpack", 8 | "clean": "rimraf lib", 9 | "prepublish": "npm run build", 10 | "lint": "eslint", 11 | "start": "NODE_ENV=development webpack-dev-server --mode=development --hot", 12 | "test": "NODE_ENV=test jest" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/MingruiZhang/react-animate-mount.git" 17 | }, 18 | "author": "Mingrui Zhang", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/MingruiZhang/react-animate-mount/issues" 22 | }, 23 | "homepage": "https://github.com/MingruiZhang/react-animate-mount#readme", 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-core": "^6.26.3", 27 | "babel-eslint": "^8.2.5", 28 | "babel-jest": "^23.2.0", 29 | "babel-loader": "^7.1.4", 30 | "babel-preset-env": "^1.7.0", 31 | "babel-preset-flow": "^6.23.0", 32 | "babel-preset-react": "^6.24.1", 33 | "babel-preset-stage-2": "^6.24.1", 34 | "css-loader": "^0.28.11", 35 | "enzyme": "^3.3.0", 36 | "enzyme-adapter-react-16": "^1.1.1", 37 | "eslint": "^5.0.0", 38 | "eslint-config-prettier": "^2.9.0", 39 | "eslint-plugin-flowtype": "^2.49.3", 40 | "eslint-plugin-import": "^2.13.0", 41 | "eslint-plugin-prettier": "^2.6.1", 42 | "eslint-plugin-promise": "^3.8.0", 43 | "eslint-plugin-react": "^7.9.1", 44 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 45 | "flow-bin": "^0.75.0", 46 | "html-webpack-plugin": "^3.2.0", 47 | "jest": "^23.2.0", 48 | "path": "^0.12.7", 49 | "prettier": "^1.13.5", 50 | "react": "^16.4.1", 51 | "react-dom": "^16.4.1", 52 | "rimraf": "^2.6.2", 53 | "style-loader": "^0.21.0", 54 | "webpack": "^4.12.2", 55 | "webpack-cli": "^3.0.8", 56 | "webpack-dev-server": "^3.1.4" 57 | }, 58 | "dependencies": {}, 59 | "peerDependencies": { 60 | "react": ">=16.3.0", 61 | "react-dom": ">=16.3.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AnimateGroup.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import { Animate } from './Animate'; 4 | import mergeDiff, { type ChildWithStatus, Status } from './utils/mergeDiff'; 5 | 6 | type Props = {| 7 | children: React.Node, 8 | duration?: number, 9 | onAnimateComplete?: () => void, 10 | type?: 'slide' | 'fade' 11 | |}; 12 | 13 | type State = {| 14 | isAnimating: boolean, 15 | renderChildren: Array 16 | |}; 17 | 18 | class AnimateGroup extends React.Component { 19 | _mounted: boolean; 20 | 21 | state = { 22 | isAnimating: false, 23 | renderChildren: React.Children.toArray(this.props.children).map(child => ({ 24 | status: Status.static, 25 | child 26 | })) 27 | }; 28 | 29 | static getDerivedStateFromProps(nextProps: Props, prevState: State) { 30 | const newChildrenWithState = mergeDiff( 31 | prevState.renderChildren.map(childWithState => childWithState.child), 32 | React.Children.toArray(nextProps.children) 33 | ); 34 | 35 | if (!newChildrenWithState.every(child => child.status === Status.static)) { 36 | return { renderChildren: newChildrenWithState, isAnimating: true }; 37 | } else { 38 | return null; 39 | } 40 | } 41 | 42 | componentDidMount() { 43 | this._mounted = true; 44 | } 45 | 46 | componentWillUnmount() { 47 | this._mounted = false; 48 | } 49 | 50 | render() { 51 | const { type, duration } = this.props; 52 | const { renderChildren } = this.state; 53 | return renderChildren.map(({ status, child }) => { 54 | return ( 55 | 63 | {child} 64 | 65 | ); 66 | }); 67 | } 68 | 69 | _handleEachAnimateComplete = () => { 70 | const { isAnimating } = this.state; 71 | const { onAnimateComplete, children } = this.props; 72 | if (isAnimating) { 73 | onAnimateComplete && onAnimateComplete(); 74 | this.setState({ 75 | renderChildren: React.Children.toArray(children).map(child => ({ 76 | status: Status.static, 77 | child 78 | })), 79 | isAnimating: false 80 | }); 81 | } 82 | }; 83 | 84 | _requestNewFrame = (cb: () => void) => { 85 | window.requestAnimationFrame(() => { 86 | window.requestAnimationFrame(() => { 87 | if (this._mounted) { 88 | cb(); 89 | } 90 | }); 91 | }); 92 | }; 93 | } 94 | 95 | export default AnimateGroup; 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Animate Mount 2 | 3 | [![npm version](https://img.shields.io/npm/v/react-animate-mount.svg)](https://www.npmjs.com/package/react-animate-mount) 4 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/mingruizhang/react-animate-mount/blob/master/LICENSE) 5 | 6 | Simple and light wrapper component for sliding (to auto height) and fading animation when children mounts and unmount: 7 | * [**Animate**](https://github.com/MingruiZhang/react-animate-mount#animate) - provides sliding and fading animation for single component when mount & umount 8 | * [**AnimateGroup**](https://github.com/MingruiZhang/react-animate-mount#animategroup) - provides animation for items entering & leaving a list. 9 | 10 | ![Gif Demo](https://media.giphy.com/media/1ZDtRxrFcosjQRyAC5/giphy.gif) 11 | 12 | #### Install 13 | 14 | ```bash 15 | $ yarn add react-animate-mount 16 | ``` 17 | 18 | #### Demos 19 | 20 | * https://mingruizhang.github.io/react-animate-mount/ 21 | * https://codesandbox.io/s/303lzkow5 22 | * https://codesandbox.io/s/zrwyk6l933 23 | 24 | 25 | ## Animate 26 | 27 | Help animate single component when mount & umount 28 | 29 | #### Usage 30 | 31 | ``` 32 | /** Before **/ 33 | this.state.show ? {childen} : null; 34 | 35 | /** After **/ 36 | import { Animate } from 'react-animate-mount' 37 | 38 | 39 | {childen} 40 | 41 | ``` 42 | 43 | #### Props 44 | 45 | | Name | Type | Description | 46 | | :------------------------------------------------- | :------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 47 | | **show** | boolean | The key boolean that indicate if the children should be mounted | 48 | | **appear** | ?boolean = false | Normally component is not animated when `` mounts, with this flag the child component will animate in on initialization. | 49 | | **duration** | ?number = 200 | The duration you want the animate to last in ms, default to 250 | 50 | | **type** | ?string('slide' or 'fade') = 'slide' | Specify animation effect, sliding or pure fading | 51 | | **onAnimateComplete** | ?function | Invokes when component animation finishes. | 52 | 53 | 54 | ## AnimateGroup 55 | 56 | Wraps around a list of item, this provides `` effect to each individual child. As item changes in the list, `` will animate out removed children and animate in new children correctly. 57 | 58 | Please provide unique key to each child. keys are used to identify child between renders 59 | 60 | #### Usage 61 | 62 | ``` 63 | import { AnimateGroup } from 'react-animate-mount' 64 | 65 | 66 | {items.map(item => ()} 67 | 68 | ``` 69 | 70 | #### Props 71 | 72 | | Name | Type | Description | 73 | | :------------------------------------------------- | :------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 74 | | **duration** | ?number = 200 | The duration you want the animate to last in ms, default to 250 | 75 | | **type** | ?string('slide' or 'fade') = 'slide' | Specify animation effect, sliding or pure fading | 76 | | **onAnimateComplete** | ?function | Invokes when each time animation finishes. | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /demo/examples/AnimateGroup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import AnimateGroup from '../../src/AnimateGroup' 4 | 5 | let nameInc = 7; 6 | 7 | const colorPalette = [ 8 | 'rgba(255, 202, 177, 0.4)', 9 | 'rgba(105, 162, 176, 0.4)', 10 | 'rgba(101, 145, 87, 0.4)', 11 | 'rgba(161, 192, 132, 0.4)', 12 | 'rgba(224, 82, 99, 0.4)', 13 | 'rgba(31, 39, 27, 0.4)', 14 | 'rgba(25, 100, 126, 0.4)', 15 | 'rgba(40, 175, 176, 0.4)', 16 | 'rgba(244, 211, 94, 0.4)', 17 | 'rgba(238, 150, 75, 0.4)' 18 | ]; 19 | 20 | const randomMode = ['AddRemove']; 21 | 22 | const itemStyle = { 23 | display: 'flex', 24 | flexDirection: 'column', 25 | marginTop: 10, 26 | marginBottom: 10, 27 | padding: 5, 28 | alignItems: 'center', 29 | borderRadius: 10 30 | }; 31 | 32 | const textStyle = { 33 | fontSize: 16, 34 | fontWeight: 'bold' 35 | }; 36 | 37 | class ExampleAnimateGroup extends React.PureComponent { 38 | state = { 39 | items: [ 40 | { 41 | name: 1, 42 | bgc: colorPalette[0] 43 | }, 44 | { 45 | name: 2, 46 | bgc: colorPalette[1] 47 | }, 48 | { 49 | name: 3, 50 | bgc: colorPalette[2] 51 | }, 52 | { 53 | name: 4, 54 | bgc: colorPalette[3] 55 | }, 56 | { 57 | name: 5, 58 | bgc: colorPalette[4] 59 | } 60 | ] 61 | }; 62 | 63 | _handleRandomAction = () => { 64 | const { items } = this.state; 65 | const mode = randomMode[this._getRandomInt(3)]; 66 | if (items.length < 3) { 67 | this._randomAdd(); 68 | } else if (items.length > 6) { 69 | this._randomRemove(); 70 | } else if (mode === 'Add') { 71 | this._randomAdd(); 72 | } else if (mode === 'Remove') { 73 | this._randomRemove(); 74 | } else { 75 | this._randomAddRemove(); 76 | } 77 | }; 78 | 79 | _randomAdd = () => { 80 | const { items } = this.state; 81 | const randomPosition = this._getRandomInt(items.length); 82 | this.setState({ 83 | items: [...items.slice(0, randomPosition), this._generateNewItem(), ...items.slice(randomPosition)] 84 | }); 85 | }; 86 | 87 | _randomRemove = () => { 88 | const { items } = this.state; 89 | const randomPosition = this._getRandomInt(items.length); 90 | this.setState({ 91 | items: [...items.slice(0, randomPosition), ...items.slice(randomPosition + 1)] 92 | }); 93 | }; 94 | 95 | _randomAddRemove = () => { 96 | const { items } = this.state; 97 | const randomRemovePosition = this._getRandomInt(items.length); 98 | const itemsTemp = [...items.slice(0, randomRemovePosition), ...items.slice(randomRemovePosition + 1)]; 99 | const randomAddPosition = this._getRandomInt(itemsTemp.length); 100 | this.setState({ 101 | items: [...itemsTemp.slice(0, randomAddPosition), this._generateNewItem(), ...itemsTemp.slice(randomAddPosition)] 102 | }); 103 | }; 104 | 105 | _getRandomInt = (max: number) => { 106 | return Math.floor(Math.random() * Math.floor(max)); 107 | }; 108 | 109 | _generateNewItem = () => ({ 110 | name: nameInc++, 111 | bgc: colorPalette[nameInc % 10] 112 | }); 113 | 114 | render() { 115 | const { items } = this.state; 116 | return ( 117 |
118 |
119 | Add/Remove item 120 |
121 | 122 | {items.map(item => ( 123 |
124 |

{item.name}

125 |
126 | ))} 127 |
128 |
129 | ); 130 | } 131 | } 132 | 133 | export default ExampleAnimateGroup; 134 | -------------------------------------------------------------------------------- /src/utils/mergeDiff.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | 4 | type StatusEnum = $Values; 5 | export const Status = Object.freeze({ 6 | in: 'in', // Child is animating in 7 | out: 'out', // Child is animating out 8 | static: 'static' // Child is from initial render or have animated in 9 | }); 10 | 11 | export type ChildWithStatus = {| 12 | status: StatusEnum, 13 | child: React.Element<*> 14 | |}; 15 | 16 | /** 17 | * Core merging algorithm: Topological sorting (https://en.wikipedia.org/wiki/Topological_sorting) 18 | * 19 | * If previous keys are [A, C] and the next render keys are [B, C, D], the merged keys should have 20 | * - A, B must be before C, order of A and B can be ambiguous 21 | * - D must be after C 22 | * 23 | * The result here could either be [A, B, C, D] or [B, A, C, D]. We will choose to always prioritize previous keys, so the final result here will be [A, B, C, D]. 24 | * 25 | * We picked to use an insertion sort instead of Kahn's algorithm and can achieve closely O(n+m) complexity for the following reasons: 26 | * 1. Essentially we are merging only two path. 27 | * 2. The children we recieved from both render are already in order. 28 | * 3. Most use cases for this function will have a diff that is limited in size. (add/remove one item) 29 | * 30 | * However, note that the complexity can be O(n*m) in the case that there are no similarities between the two input lists. 31 | */ 32 | export default ( 33 | prevChildren: Array>, 34 | nextChildren: Array> 35 | ): Array => { 36 | let nextChildIndex = 0; 37 | let prevChildIndex = 0; 38 | const mergedChildren = []; 39 | while (prevChildIndex < prevChildren.length) { 40 | if (nextChildIndex >= nextChildren.length) { 41 | // in this case, we've exhausted all next children and merge the rest of prev children 42 | mergedChildren.push(...prevChildren.slice(prevChildIndex).map(child => ({ child, status: Status.out }))); 43 | break; 44 | } 45 | const nextChild = nextChildren[nextChildIndex]; 46 | const prevChild = prevChildren[prevChildIndex]; 47 | 48 | if (nextChild.key === prevChild.key) { 49 | // if keys are equal, the child is static and has not transitioned between states 50 | mergedChildren.push({ child: nextChild, status: Status.static }); 51 | nextChildIndex++; 52 | } else { 53 | // otherwise, we check for membership of prevChild in nextChildren 54 | const prevChildIndexInNextChildren = nextChildren.findIndex(child => child.key === prevChild.key); 55 | if (prevChildIndexInNextChildren >= 0) { 56 | // if prevChild is in nextChildren, then the elements between nextChildIndex and prevChildIndexInNextChildren are transitioning in; thus we insert them 57 | // and we also push the matching prevChild and nextChild at prevChildIndexInNextChildren + 1 58 | // then move the indices accordingly. 59 | mergedChildren.push( 60 | ...nextChildren 61 | .slice(nextChildIndex, prevChildIndexInNextChildren) 62 | .map(child => ({ child, status: Status.in })), 63 | { child: nextChildren[prevChildIndexInNextChildren], status: Status.static } 64 | ); 65 | // Push the prevChild (also nextChildren at prevChildIndexInNextChildren) 66 | nextChildIndex = prevChildIndexInNextChildren + 1; 67 | } else { 68 | // if prevChild is not in next children, that child is leaving. 69 | mergedChildren.push({ child: prevChild, status: Status.out }); 70 | } 71 | } 72 | prevChildIndex++; 73 | } 74 | 75 | if (nextChildIndex < nextChildren.length) { 76 | // in this case, we've exhausted all prev children and merge the rest of next children 77 | mergedChildren.push(...nextChildren.slice(nextChildIndex).map(child => ({ child, status: Status.in }))); 78 | } 79 | 80 | return mergedChildren; 81 | }; 82 | -------------------------------------------------------------------------------- /demo/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Raleway', sans-serif; 3 | letter-spacing: 1px; 4 | text-align: center; 5 | margin: 0; 6 | height: 100%; 7 | } 8 | 9 | a, 10 | a:visited, 11 | a:hover, 12 | a:active { 13 | color: inherit; 14 | text-decoration: none; 15 | } 16 | 17 | .root { 18 | max-width: 960px; 19 | margin: 0 auto; 20 | margin-bottom: 50px; 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | .header { 26 | font-weight: 300; 27 | font-size: 36px; 28 | margin-left: 20px; 29 | margin-right: 20px; 30 | } 31 | 32 | .header-sub { 33 | font-weight: 200; 34 | font-size: 16px; 35 | margin: 10px 20px 30px; 36 | } 37 | 38 | .nav { 39 | padding: 0; 40 | display: flex; 41 | flex-direction: row; 42 | justify-content: space-around; 43 | list-style-type: none; 44 | text-decoration: underline; 45 | } 46 | 47 | .floating-nav-fixed { 48 | z-index: 100; 49 | position: fixed; 50 | background-color: white; 51 | width: 100%; 52 | box-shadow: rgba(0, 0, 0, 0.12) 0px 4px 4px 0px; 53 | } 54 | 55 | .floating-nav { 56 | padding: 15px 0; 57 | margin: 0; 58 | display: flex; 59 | flex-direction: row-reverse; 60 | justify-content: space-between; 61 | } 62 | 63 | .floating-left { 64 | margin-left: 40px; 65 | } 66 | 67 | .floating-right { 68 | margin: 0; 69 | padding: 0; 70 | display: flex; 71 | flex-direction: row; 72 | justify-content: space-between; 73 | align-items: center; 74 | list-style-type: none; 75 | } 76 | 77 | .right-margin { 78 | margin-right: 20px; 79 | } 80 | 81 | .floating-right li { 82 | margin-right: 20px; 83 | font-size: 14px; 84 | } 85 | 86 | .section { 87 | display: flex; 88 | flex-direction: column; 89 | align-items: center; 90 | margin: 40px 20px; 91 | } 92 | 93 | .section .section-header { 94 | font-weight: 300; 95 | } 96 | 97 | .section .section-text { 98 | font-weight: 200; 99 | font-size: 14px; 100 | } 101 | 102 | .code-container { 103 | max-width: 500px; 104 | width: 100%; 105 | display: flex; 106 | flex-direction: column; 107 | border: 1px solid black; 108 | border-radius: 10px; 109 | overflow: hidden; 110 | margin: 20px 0 30px; 111 | box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 4px 0px, rgba(0, 0, 0, 0.12) 0px 4px 4px 0px; 112 | } 113 | 114 | .code { 115 | display: flex; 116 | flex-direction: column; 117 | justify-content: flex-start; 118 | padding: 20px 50px; 119 | font-family: 'Inconsolata', monospace; 120 | line-height: 1.4rem; 121 | font-size: 14px; 122 | background-color: black; 123 | color: white; 124 | white-space: pre-wrap; 125 | text-align: left; 126 | } 127 | 128 | .code-red { 129 | color: rgba(242, 112, 118, 100); 130 | } 131 | 132 | .code-purple { 133 | color: rgba(200, 143, 236, 100); 134 | } 135 | 136 | .example-root { 137 | margin: 20px; 138 | display: flex; 139 | flex-direction: column; 140 | justify-content: center; 141 | position: relative; 142 | } 143 | 144 | .example-toggle { 145 | -webkit-user-select: none; /* Safari 3.1+ */ 146 | -moz-user-select: none; /* Firefox 2+ */ 147 | -ms-user-select: none; /* IE 10+ */ 148 | user-select: none; 149 | margin: 0 0 20px; 150 | padding: 10px 20px; 151 | border-radius: 10px; 152 | font-weight: bold; 153 | border: 1px solid black; 154 | transition: background-color 0.15s ease; 155 | cursor: pointer; 156 | } 157 | 158 | .example-toggle:hover, 159 | .example-toggle:active { 160 | background-color: rgba(113, 129, 141, 0.15); 161 | } 162 | 163 | .example-content { 164 | text-align: center; 165 | background: rgba(105, 162, 176, 0.4); 166 | padding: 20px; 167 | border-radius: 10px; 168 | } 169 | 170 | .example-animate { 171 | text-align: center; 172 | background: rgba(255, 202, 177, 0.4); 173 | padding: 20px; 174 | margin-bottom: 20px; 175 | border-radius: 10px; 176 | } 177 | 178 | .example-animate-2 { 179 | text-align: center; 180 | background: rgba(255, 202, 177, 0.4); 181 | padding: 20px; 182 | margin-bottom: 20px; 183 | border-radius: 10px; 184 | box-sizing: border-box; 185 | background-color: rgba(224, 197, 241, 100); 186 | box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 4px 0px, rgba(0, 0, 0, 0.12) 0px 4px 4px 0px; 187 | 188 | } 189 | 190 | .floating { 191 | top: -125px; 192 | width: 100%; 193 | position: absolute; 194 | z-index: 10; 195 | } 196 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Raleway', sans-serif; 3 | letter-spacing: 1px; 4 | text-align: center; 5 | margin: 0; 6 | height: 100%; 7 | } 8 | 9 | a, 10 | a:visited, 11 | a:hover, 12 | a:active { 13 | color: inherit; 14 | text-decoration: none; 15 | } 16 | 17 | .root { 18 | max-width: 960px; 19 | margin: 0 auto; 20 | margin-bottom: 50px; 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | .header { 26 | font-weight: 300; 27 | font-size: 36px; 28 | margin-left: 20px; 29 | margin-right: 20px; 30 | } 31 | 32 | .header-sub { 33 | font-weight: 200; 34 | font-size: 16px; 35 | margin: 10px 20px 30px; 36 | } 37 | 38 | .nav { 39 | padding: 0; 40 | display: flex; 41 | flex-direction: row; 42 | justify-content: space-around; 43 | list-style-type: none; 44 | text-decoration: underline; 45 | } 46 | 47 | .floating-nav-fixed { 48 | z-index: 100; 49 | position: fixed; 50 | background-color: white; 51 | width: 100%; 52 | box-shadow: rgba(0, 0, 0, 0.12) 0px 4px 4px 0px; 53 | } 54 | 55 | .floating-nav { 56 | padding: 15px 0; 57 | margin: 0; 58 | display: flex; 59 | flex-direction: row-reverse; 60 | justify-content: space-between; 61 | } 62 | 63 | .floating-left { 64 | margin-left: 40px; 65 | } 66 | 67 | .floating-right { 68 | margin: 0; 69 | padding: 0; 70 | display: flex; 71 | flex-direction: row; 72 | justify-content: space-between; 73 | align-items: center; 74 | list-style-type: none; 75 | } 76 | 77 | .right-margin { 78 | margin-right: 20px; 79 | } 80 | 81 | .floating-right li { 82 | margin-right: 20px; 83 | font-size: 14px; 84 | } 85 | 86 | .section { 87 | display: flex; 88 | flex-direction: column; 89 | align-items: center; 90 | margin: 40px 20px; 91 | } 92 | 93 | .section .section-header { 94 | font-weight: 300; 95 | } 96 | 97 | .section .section-text { 98 | font-weight: 200; 99 | font-size: 14px; 100 | } 101 | 102 | .code-container { 103 | max-width: 500px; 104 | width: 100%; 105 | display: flex; 106 | flex-direction: column; 107 | border: 1px solid black; 108 | border-radius: 10px; 109 | overflow: hidden; 110 | margin: 20px 0 30px; 111 | box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 4px 0px, rgba(0, 0, 0, 0.12) 0px 4px 4px 0px; 112 | } 113 | 114 | .code { 115 | display: flex; 116 | flex-direction: column; 117 | justify-content: flex-start; 118 | padding: 20px 50px; 119 | font-family: 'Inconsolata', monospace; 120 | line-height: 1.4rem; 121 | font-size: 14px; 122 | background-color: black; 123 | color: white; 124 | white-space: pre-wrap; 125 | text-align: left; 126 | } 127 | 128 | .code-red { 129 | color: rgba(242, 112, 118, 100); 130 | } 131 | 132 | .code-purple { 133 | color: rgba(200, 143, 236, 100); 134 | } 135 | 136 | .example-root { 137 | margin: 20px; 138 | display: flex; 139 | flex-direction: column; 140 | justify-content: center; 141 | position: relative; 142 | } 143 | 144 | .example-toggle { 145 | -webkit-user-select: none; /* Safari 3.1+ */ 146 | -moz-user-select: none; /* Firefox 2+ */ 147 | -ms-user-select: none; /* IE 10+ */ 148 | user-select: none; 149 | margin: 0 0 20px; 150 | padding: 10px 20px; 151 | border-radius: 10px; 152 | font-weight: bold; 153 | border: 1px solid black; 154 | transition: background-color 0.15s ease; 155 | cursor: pointer; 156 | } 157 | 158 | .example-toggle:hover, 159 | .example-toggle:active { 160 | background-color: rgba(113, 129, 141, 0.15); 161 | } 162 | 163 | .example-content { 164 | text-align: center; 165 | background: rgba(105, 162, 176, 0.4); 166 | padding: 20px; 167 | border-radius: 10px; 168 | } 169 | 170 | .example-animate { 171 | text-align: center; 172 | background: rgba(255, 202, 177, 0.4); 173 | padding: 20px; 174 | margin-bottom: 20px; 175 | border-radius: 10px; 176 | } 177 | 178 | .example-animate-2 { 179 | text-align: center; 180 | background: rgba(255, 202, 177, 0.4); 181 | padding: 20px; 182 | margin-bottom: 20px; 183 | border-radius: 10px; 184 | box-sizing: border-box; 185 | background-color: rgba(224, 197, 241, 100); 186 | box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 4px 0px, rgba(0, 0, 0, 0.12) 0px 4px 4px 0px; 187 | 188 | } 189 | 190 | .floating { 191 | top: -125px; 192 | width: 100%; 193 | position: absolute; 194 | z-index: 10; 195 | } 196 | -------------------------------------------------------------------------------- /test/Animate.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import Animate from '../src/Animate'; 4 | import { mount } from 'enzyme'; 5 | 6 | const TestChildren =
; 7 | const onCompleteFn = jest.fn(); 8 | const outerLayerNode = 'outerLayerNode'; 9 | 10 | const validateStaticMounted = component => { 11 | expect(component.state('animateStage')).toBe('static'); 12 | expect(onCompleteFn).toBeCalled(); 13 | expect(component.find({ testid: 'children' })).toHaveLength(1); 14 | }; 15 | 16 | const validateStaticUnmounted = component => { 17 | expect(component.state('animateStage')).toBe('static'); 18 | expect(onCompleteFn).toBeCalled(); 19 | component.update(); 20 | expect(component.find({ testid: 'children' })).toHaveLength(0); 21 | }; 22 | 23 | describe('Animate', () => { 24 | jest.useFakeTimers(); 25 | window.requestAnimationFrame = cb => { 26 | setTimeout(cb, 0); 27 | }; 28 | 29 | describe('initial render', () => { 30 | test('be null when show=false', () => { 31 | const component = new Animate({ show: false, children: TestChildren, duration: 200, type: 'slide' }); 32 | expect(component.render()).toBeNull(); 33 | }); 34 | 35 | test('be children 2 wrapped View when show=true', () => { 36 | const component = new Animate({ show: true, children: TestChildren, duration: 200, type: 'slide' }); 37 | expect(component.render()).toEqual( 38 |
39 |
{TestChildren}
40 |
41 | ); 42 | }); 43 | }); 44 | 45 | describe('mounting transition', () => { 46 | let component; 47 | let transitionEndCallback; 48 | 49 | beforeEach(() => { 50 | component = mount( 51 | 52 | {TestChildren} 53 | 54 | ); 55 | component.instance()._setInnerLayerNode = jest.fn(); 56 | expect(component.find({ testid: 'children' })).toHaveLength(0); 57 | expect(component.state('animateStage')).toBe('static'); 58 | component.setProps({ show: true }); 59 | // animateStage change to 'prep' 60 | expect(component.state('animateStage')).toBe('prep'); 61 | expect(component.find({ testid: 'children' })).toHaveLength(1); 62 | const outerLayer = component.children(); 63 | transitionEndCallback = outerLayer.prop('onTransitionEnd'); 64 | // animateStage change to 'animate' 65 | component.instance()._transitionStart({ componentHeight: 300 }); 66 | expect(component.state('animateStage')).toBe('animate'); 67 | expect(component.state('animateProps')).toEqual({ height: 0, opacity: 0 }); 68 | jest.advanceTimersByTime(5); 69 | expect(component.state('animateProps')).toEqual({ height: 300, opacity: 1 }); 70 | component.instance()._outerLayerNode = outerLayerNode; 71 | }); 72 | 73 | test('basic prep -> animate -> static flow', () => { 74 | // css transition ends 75 | transitionEndCallback({ target: outerLayerNode }); 76 | 77 | validateStaticMounted(component); 78 | }); 79 | 80 | test('intecepted by un-mount during animate', () => { 81 | // new prop came in before transition finishes 82 | component.setProps({ show: false }); 83 | // animateStage change to 'animate' but different firection 84 | expect(component.state('animateStage')).toBe('animate'); 85 | expect(component.state('animateProps')).toEqual({ height: 0, opacity: 0 }); 86 | transitionEndCallback({ target: outerLayerNode }); 87 | 88 | validateStaticUnmounted(component); 89 | }); 90 | }); 91 | 92 | describe('un-mounting transition', () => { 93 | let component; 94 | let transitionEndCallback; 95 | 96 | beforeEach(() => { 97 | component = mount( 98 | 99 | {TestChildren} 100 | 101 | ); 102 | component.instance()._setInnerLayerNode = jest.fn(); 103 | expect(component.find({ testid: 'children' })).toHaveLength(1); 104 | expect(component.state('animateStage')).toBe('static'); 105 | component.setProps({ show: false }); 106 | // animateStage change to 'prep' 107 | expect(component.state('animateStage')).toBe('prep'); 108 | expect(component.find({ testid: 'children' })).toHaveLength(1); 109 | const outerLayer = component.children(); 110 | transitionEndCallback = outerLayer.prop('onTransitionEnd'); 111 | component.instance()._transitionStart({ componentHeight: 300 }); 112 | // animateStage change to 'animate' 113 | expect(component.state('animateStage')).toBe('animate'); 114 | expect(component.state('animateProps')).toEqual({ height: 300, opacity: 1 }); 115 | jest.advanceTimersByTime(5); 116 | expect(component.state('animateProps')).toEqual({ height: 0, opacity: 0 }); 117 | component.instance()._outerLayerNode = outerLayerNode; 118 | }); 119 | 120 | test('basic prep -> animate -> static flow', () => { 121 | // css transition ends 122 | transitionEndCallback({ target: outerLayerNode }); 123 | 124 | validateStaticUnmounted(component); 125 | }); 126 | 127 | test('intecepted by mount during animate', () => { 128 | // new prop came in before transition finishes 129 | component.setProps({ show: true }); 130 | // animateStage change to 'animate' but different firection 131 | expect(component.state('animateStage')).toBe('animate'); 132 | expect(component.state('animateProps')).toEqual({ height: 300, opacity: 1 }); 133 | transitionEndCallback({ target: outerLayerNode }); 134 | 135 | validateStaticMounted(component); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /test/AnimateGroup.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // eslint-env jest 3 | import * as React from 'react'; 4 | import AnimateGroup from '../src/AnimateGroup'; 5 | import { mount } from 'enzyme'; 6 | import mergeDiff, { Status } from '../src/utils/mergeDiff'; 7 | 8 | const textArrayInit = [ 9 |
Text 1
, 10 |
Text 2
, 11 |
Text 3
, 12 |
Text 4
, 13 |
Text 5
14 | ]; 15 | 16 | describe('AnimateGroup', () => { 17 | describe('mergeDiff', () => { 18 | test('merge correctly when adding new item in middle', () => { 19 | // Added 'Text 6' betweet 3 and 4 20 | const textArrayNew = [ 21 |
Text 1
, 22 |
Text 2
, 23 |
Text 3
, 24 |
Text 6
, 25 |
Text 4
, 26 |
Text 5
27 | ]; 28 | 29 | expect(mergeDiff(textArrayInit, textArrayNew)).toEqual([ 30 | { child:
Text 1
, status: Status.static }, 31 | { child:
Text 2
, status: Status.static }, 32 | { child:
Text 3
, status: Status.static }, 33 | { child:
Text 6
, status: Status.in }, 34 | { child:
Text 4
, status: Status.static }, 35 | { child:
Text 5
, status: Status.static } 36 | ]); 37 | }); 38 | 39 | test('merge correctly when adding new item at end', () => { 40 | // Added 'Text 6' after 5 41 | const textArrayNew = [ 42 |
Text 1
, 43 |
Text 2
, 44 |
Text 3
, 45 |
Text 4
, 46 |
Text 5
, 47 |
Text 6
48 | ]; 49 | 50 | expect(mergeDiff(textArrayInit, textArrayNew)).toEqual([ 51 | { child:
Text 1
, status: Status.static }, 52 | { child:
Text 2
, status: Status.static }, 53 | { child:
Text 3
, status: Status.static }, 54 | { child:
Text 4
, status: Status.static }, 55 | { child:
Text 5
, status: Status.static }, 56 | { child:
Text 6
, status: Status.in } 57 | ]); 58 | }); 59 | 60 | test('merge correctly when removing item in middle', () => { 61 | // Removed 'Text 2' 62 | const textArrayNew = [ 63 |
Text 1
, 64 |
Text 3
, 65 |
Text 4
, 66 |
Text 5
67 | ]; 68 | 69 | expect(mergeDiff(textArrayInit, textArrayNew)).toEqual([ 70 | { child:
Text 1
, status: Status.static }, 71 | { child:
Text 2
, status: Status.out }, 72 | { child:
Text 3
, status: Status.static }, 73 | { child:
Text 4
, status: Status.static }, 74 | { child:
Text 5
, status: Status.static } 75 | ]); 76 | }); 77 | 78 | test('merge correctly when removing item at end', () => { 79 | // Removed 'Text 5' 80 | const textArrayNew = [ 81 |
Text 1
, 82 |
Text 2
, 83 |
Text 3
, 84 |
Text 4
85 | ]; 86 | 87 | expect(mergeDiff(textArrayInit, textArrayNew)).toEqual([ 88 | { child:
Text 1
, status: Status.static }, 89 | { child:
Text 2
, status: Status.static }, 90 | { child:
Text 3
, status: Status.static }, 91 | { child:
Text 4
, status: Status.static }, 92 | { child:
Text 5
, status: Status.out } 93 | ]); 94 | }); 95 | 96 | test('merge correctly when both adding and removing item', () => { 97 | // Removed 2 and 4 ,Added 6 and 7 98 | const textArrayNew = [ 99 |
Text 1
, 100 |
Text 7
, 101 |
Text 3
, 102 |
Text 5
, 103 |
Text 6
104 | ]; 105 | 106 | expect(mergeDiff(textArrayInit, textArrayNew)).toEqual([ 107 | { child:
Text 1
, status: Status.static }, 108 | { child:
Text 2
, status: Status.out }, 109 | { child:
Text 7
, status: Status.in }, 110 | { child:
Text 3
, status: Status.static }, 111 | { child:
Text 4
, status: Status.out }, 112 | { child:
Text 5
, status: Status.static }, 113 | { child:
Text 6
, status: Status.in } 114 | ]); 115 | }); 116 | }); 117 | 118 | test('onAnimateComplete gets triggered only when all animate finishesComplete', () => { 119 | const onCompleteStub = jest.fn(); 120 | const component = mount({textArrayInit}); 121 | const textArrayNew = [ 122 |
Text 1
, 123 |
Text 7
, 124 |
Text 3
, 125 |
Text 5
, 126 |
Text 6
127 | ]; 128 | component.setProps({ children: textArrayNew }); 129 | expect(component.state('isAnimating')).toEqual(true); 130 | component.instance()._handleEachAnimateComplete(); 131 | expect(component.state('isAnimating')).toEqual(false); 132 | expect(onCompleteStub).toBeCalled(); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /src/Animate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | type AnimateStageEnum = $Values; 6 | const AnimateStage = Object.freeze({ 7 | animate: 'animate', 8 | static: 'static', 9 | prep: 'prep' 10 | }); 11 | 12 | type AnimateProps = { 13 | height: number | 'auto', 14 | opacity: number 15 | }; 16 | 17 | type Props = {| 18 | appear?: boolean, 19 | children: React.Node, 20 | duration: number, 21 | onAnimateComplete?: () => void, 22 | type: 'slide' | 'fade', 23 | show: boolean 24 | |}; 25 | 26 | type State = {| 27 | renderChildren: React.Node, 28 | animateStage: AnimateStageEnum, 29 | animateProps: AnimateProps, 30 | componentHeight: number, 31 | props: Props 32 | |}; 33 | 34 | // Set inner layer to be flex to avoid collapsing margins 35 | const innerLayerStyle = { display: 'flex', flexDirection: 'column' }; 36 | const transitionStyle = duration => ({ 37 | transitionProperty: 'opacity, height', 38 | transitionTimingFunction: 'ease', 39 | overflow: 'hidden', 40 | transitionDuration: `${duration}ms` 41 | }); 42 | 43 | export class Animate extends React.Component { 44 | _mounted: boolean; 45 | _outerLayerNode: ?(Element | Text); 46 | _innerLayerNode: ?(Element | Text); 47 | 48 | static defaultProps = { 49 | duration: 250, 50 | type: 'slide' 51 | }; 52 | 53 | state = { 54 | animateStage: AnimateStage.static, 55 | animateProps: this.props.show && !this.props.appear ? { height: 'auto', opacity: 1 } : { height: 0, opacity: 0 }, 56 | renderChildren: this.props.children, 57 | componentHeight: 0, 58 | props: { 59 | ...this.props, 60 | show: this.props.appear ? !this.props.show : this.props.show 61 | } 62 | }; 63 | 64 | componentDidMount() { 65 | this._mounted = true; 66 | } 67 | 68 | componentWillUnmount() { 69 | this._mounted = false; 70 | } 71 | 72 | static getDerivedStateFromProps(nextProps: Props, prevState: State) { 73 | if (nextProps.show !== prevState.props.show) { 74 | if (prevState.animateStage === AnimateStage.static) { 75 | return { 76 | animateStage: AnimateStage.prep, 77 | renderChildren: nextProps.show ? nextProps.children : prevState.props.children, 78 | props: nextProps 79 | }; 80 | } else { 81 | return { 82 | animateStage: AnimateStage.animate, 83 | animateProps: nextProps.show ? { height: prevState.componentHeight, opacity: 1 } : { height: 0, opacity: 0 }, 84 | props: nextProps 85 | }; 86 | } 87 | } else { 88 | return { 89 | props: nextProps, 90 | renderChildren: nextProps.children 91 | }; 92 | } 93 | } 94 | 95 | render() { 96 | const { 97 | animateProps: { height, opacity }, 98 | animateStage, 99 | renderChildren, 100 | props: { show, duration } 101 | } = this.state; 102 | 103 | const isStatic = animateStage === AnimateStage.static; 104 | const isAnimate = animateStage === AnimateStage.animate; 105 | 106 | const outerLayerStyle = isAnimate ? { height, opacity, ...transitionStyle(duration) } : { height, opacity }; 107 | 108 | if (isStatic) { 109 | return show ? ( 110 |
111 |
{renderChildren}
112 |
113 | ) : null; 114 | } else { 115 | return ( 116 |
117 |
118 | {renderChildren} 119 |
120 |
121 | ); 122 | } 123 | } 124 | 125 | _transitionStart = ({ componentHeight }: Object) => { 126 | const { 127 | props: { show, type } 128 | } = this.state; 129 | const isFade = type === 'fade'; 130 | if (show) { 131 | this.setState( 132 | { 133 | animateProps: { height: isFade ? 'auto' : 0, opacity: 0 }, 134 | animateStage: AnimateStage.animate, 135 | componentHeight 136 | }, 137 | this._requestNewFrame(() => { 138 | this.setState({ 139 | animateProps: { height: isFade ? 'auto' : componentHeight, opacity: 1 } 140 | }); 141 | }) 142 | ); 143 | } else { 144 | this.setState( 145 | { 146 | animateProps: { height: isFade ? 'auto' : componentHeight, opacity: 1 }, 147 | animateStage: AnimateStage.animate, 148 | componentHeight 149 | }, 150 | this._requestNewFrame(() => { 151 | this.setState({ 152 | animateProps: { height: isFade ? 'auto' : 0, opacity: 0 } 153 | }); 154 | }) 155 | ); 156 | } 157 | }; 158 | 159 | _handleTransitionEnd = (event: SyntheticTransitionEvent<*>) => { 160 | const { onAnimateComplete, show } = this.props; 161 | // Prevent side-effect of capturing parent transition events 162 | if (event.target === this._outerLayerNode) { 163 | onAnimateComplete && onAnimateComplete(); 164 | this.setState({ 165 | animateProps: show ? { height: 'auto', opacity: 1 } : { height: 0, opacity: 0 }, 166 | animateStage: AnimateStage.static 167 | }); 168 | } 169 | }; 170 | 171 | _requestNewFrame = (cb: () => void) => { 172 | window.requestAnimationFrame(() => { 173 | window.requestAnimationFrame(() => { 174 | if (this._mounted) { 175 | cb(); 176 | } 177 | }); 178 | }); 179 | }; 180 | 181 | _setOuterLayerNode = (elementRef: ?React.ElementRef<*>) => { 182 | this._outerLayerNode = ReactDOM.findDOMNode(elementRef); 183 | }; 184 | 185 | _setInnerLayerNode = (elementRef: ?React.ElementRef<*>) => { 186 | const innerLayerNode = ReactDOM.findDOMNode(elementRef); 187 | if (innerLayerNode instanceof window.HTMLElement) { 188 | const height = innerLayerNode.getBoundingClientRect().height; 189 | this._transitionStart({ componentHeight: height }); 190 | } 191 | }; 192 | } 193 | 194 | export default Animate; 195 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Animate } from '../src/Animate'; 4 | 5 | import ExampleAnimate1 from './examples/Animate-1'; 6 | import ExampleAnimate2 from './examples/Animate-2'; 7 | import ExampleAnimateGroup from './examples/AnimateGroup'; 8 | import CodeExample from './CodeExample'; 9 | 10 | import './styles.css'; 11 | 12 | class App extends React.Component { 13 | state = { floatingNav: false, narrowScreen: false }; 14 | 15 | componentDidMount() { 16 | window.onscroll = () => { 17 | const { floatingNav } = this.state; 18 | if (window.pageYOffset >= 180 && !floatingNav) { 19 | this.setState({ floatingNav: true }); 20 | } 21 | if (window.pageYOffset < 180 && floatingNav) { 22 | this.setState({ floatingNav: false }); 23 | } 24 | }; 25 | 26 | if (window.innerWidth < 800) { 27 | this.setState({ narrowScreen: true }); 28 | } 29 | } 30 | 31 | render() { 32 | const { floatingNav, narrowScreen } = this.state; 33 | const floatingRightCSS = narrowScreen ? 'floating-right' : 'floating-right right-margin'; 34 | return ( 35 | 36 |
37 | 38 |
39 | 56 | {narrowScreen ? null :
React Animate Mount
} 57 |
58 |
59 |
60 |
61 | 62 |

React Animate Mount

63 |

Simple and light component for animating mount and unmount

64 |
65 | 82 |
83 |

< Animate />

84 |

85 | SlideUp / SlideDown component (animate height 0 and 'auto', like this page's floating nav) 86 |

87 | 90 |
{``}
91 |
{`
`}
92 |
{`

This component slideUp and slideDown

`}
93 |
{`
`}
94 |
{``}
95 | 96 | } 97 | example={} 98 | /> 99 |

FadeIn / FadeOut component (animate opacity 0 and 1)

100 | 103 |
104 | {` 105 | {`type="fade" `} 106 | {`/>`} 107 |
108 |
{`
`}
109 |
{`

This component fadeIn and fadeOut

`}
110 |
{`
`}
111 |
{``}
112 | 113 | } 114 | example={} 115 | /> 116 |

Or simply animateIn on mount (like this page's header)

117 | 120 |
121 | {` 122 | {`appear `} 123 | {`show />`} 124 |
125 |
{`

React Animate Mount

`}
126 |
{`

Simple and light component...

`}
127 |
{``}
128 | 129 | } 130 | /> 131 |
132 |
133 |

< AnimateGroup />

134 |

Animate items when they are added / removed from a list

135 | 138 |
{``}
139 |
{` {items.map(item => ()}`}
140 |
{``}
141 | 142 | } 143 | example={} 144 | /> 145 |
146 |
147 |

Github

148 | 149 | https://github.com/MingruiZhang/react-animate-mount 150 | 151 |
152 |
153 |
154 | ); 155 | } 156 | 157 | scrollToAnimateSection = event => { 158 | event.preventDefault(); 159 | this.scrollToSection(this._animateSectionNode); 160 | }; 161 | 162 | scrollToAnimateGroupSection = event => { 163 | event.preventDefault(); 164 | this.scrollToSection(this._animateGroupSectionNode); 165 | }; 166 | 167 | scrollToGithubSection = event => { 168 | event.preventDefault(); 169 | this.scrollToSection(this._githubSectionNode); 170 | }; 171 | 172 | scrollToSection = node => { 173 | const behavior = 'scrollBehavior' in document.documentElement.style ? 'smooth' : undefined; 174 | 175 | if (node instanceof window.HTMLElement) { 176 | const { top } = node.getBoundingClientRect(); 177 | const windowTop = window.pageYOffset; 178 | window.requestAnimationFrame(() => 179 | window.scrollTo({ 180 | top: Math.max(windowTop + top - 60, 0), 181 | left: 0, 182 | behavior 183 | }) 184 | ); 185 | } 186 | }; 187 | 188 | _animateSectionRef = elementRef => { 189 | this._animateSectionNode = ReactDOM.findDOMNode(elementRef); 190 | }; 191 | 192 | _animateGroupSectionRef = elementRef => { 193 | this._animateGroupSectionNode = ReactDOM.findDOMNode(elementRef); 194 | }; 195 | 196 | _githubSectionRef = elementRef => { 197 | this._githubSectionNode = ReactDOM.findDOMNode(elementRef); 198 | }; 199 | } 200 | 201 | const rootElement = document.getElementById('root'); 202 | ReactDOM.render(, rootElement); 203 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v23.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 4cacceffd326bb118e4a3c1b4d629e98 2 | // flow-typed version: e737b9832f/jest_v23.x.x/flow_>=v0.39.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array 21 | }, 22 | /** 23 | * Resets all information stored in the mockFn.mock.calls and 24 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 25 | * up a mock's usage data between two assertions. 26 | */ 27 | mockClear(): void, 28 | /** 29 | * Resets all information stored in the mock. This is useful when you want to 30 | * completely restore a mock back to its initial state. 31 | */ 32 | mockReset(): void, 33 | /** 34 | * Removes the mock and restores the initial implementation. This is useful 35 | * when you want to mock functions in certain test cases and restore the 36 | * original implementation in others. Beware that mockFn.mockRestore only 37 | * works when mock was created with jest.spyOn. Thus you have to take care of 38 | * restoration yourself when manually assigning jest.fn(). 39 | */ 40 | mockRestore(): void, 41 | /** 42 | * Accepts a function that should be used as the implementation of the mock. 43 | * The mock itself will still record all calls that go into and instances 44 | * that come from itself -- the only difference is that the implementation 45 | * will also be executed when the mock is called. 46 | */ 47 | mockImplementation( 48 | fn: (...args: TArguments) => TReturn 49 | ): JestMockFn, 50 | /** 51 | * Accepts a function that will be used as an implementation of the mock for 52 | * one call to the mocked function. Can be chained so that multiple function 53 | * calls produce different results. 54 | */ 55 | mockImplementationOnce( 56 | fn: (...args: TArguments) => TReturn 57 | ): JestMockFn, 58 | /** 59 | * Accepts a string to use in test result output in place of "jest.fn()" to 60 | * indicate which mock function is being referenced. 61 | */ 62 | mockName(name: string): JestMockFn, 63 | /** 64 | * Just a simple sugar function for returning `this` 65 | */ 66 | mockReturnThis(): void, 67 | /** 68 | * Deprecated: use jest.fn(() => value) instead 69 | */ 70 | mockReturnValue(value: TReturn): JestMockFn, 71 | /** 72 | * Sugar for only returning a value once inside your mock 73 | */ 74 | mockReturnValueOnce(value: TReturn): JestMockFn, 75 | /** 76 | * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) 77 | */ 78 | mockResolvedValue(value: TReturn): JestMockFn>, 79 | /** 80 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) 81 | */ 82 | mockResolvedValueOnce(value: TReturn): JestMockFn>, 83 | /** 84 | * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) 85 | */ 86 | mockRejectedValue(value: TReturn): JestMockFn>, 87 | /** 88 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) 89 | */ 90 | mockRejectedValueOnce(value: TReturn): JestMockFn> 91 | }; 92 | 93 | type JestAsymmetricEqualityType = { 94 | /** 95 | * A custom Jasmine equality tester 96 | */ 97 | asymmetricMatch(value: mixed): boolean 98 | }; 99 | 100 | type JestCallsType = { 101 | allArgs(): mixed, 102 | all(): mixed, 103 | any(): boolean, 104 | count(): number, 105 | first(): mixed, 106 | mostRecent(): mixed, 107 | reset(): void 108 | }; 109 | 110 | type JestClockType = { 111 | install(): void, 112 | mockDate(date: Date): void, 113 | tick(milliseconds?: number): void, 114 | uninstall(): void 115 | }; 116 | 117 | type JestMatcherResult = { 118 | message?: string | (() => string), 119 | pass: boolean 120 | }; 121 | 122 | type JestMatcher = (actual: any, expected: any) => JestMatcherResult; 123 | 124 | type JestPromiseType = { 125 | /** 126 | * Use rejects to unwrap the reason of a rejected promise so any other 127 | * matcher can be chained. If the promise is fulfilled the assertion fails. 128 | */ 129 | rejects: JestExpectType, 130 | /** 131 | * Use resolves to unwrap the value of a fulfilled promise so any other 132 | * matcher can be chained. If the promise is rejected the assertion fails. 133 | */ 134 | resolves: JestExpectType 135 | }; 136 | 137 | /** 138 | * Jest allows functions and classes to be used as test names in test() and 139 | * describe() 140 | */ 141 | type JestTestName = string | Function; 142 | 143 | /** 144 | * Plugin: jest-enzyme 145 | */ 146 | type EnzymeMatchersType = { 147 | toBeChecked(): void, 148 | toBeDisabled(): void, 149 | toBeEmpty(): void, 150 | toBeEmptyRender(): void, 151 | toBePresent(): void, 152 | toContainReact(element: React$Element): void, 153 | toExist(): void, 154 | toHaveClassName(className: string): void, 155 | toHaveHTML(html: string): void, 156 | toHaveProp: ((propKey: string, propValue?: any) => void) & ((props: Object) => void), 157 | toHaveRef(refName: string): void, 158 | toHaveState: ((stateKey: string, stateValue?: any) => void) & ((state: Object) => void), 159 | toHaveStyle: ((styleKey: string, styleValue?: any) => void) & ((style: Object) => void), 160 | toHaveTagName(tagName: string): void, 161 | toHaveText(text: string): void, 162 | toIncludeText(text: string): void, 163 | toHaveValue(value: any): void, 164 | toMatchElement(element: React$Element): void, 165 | toMatchSelector(selector: string): void 166 | }; 167 | 168 | // DOM testing library extensions https://github.com/kentcdodds/dom-testing-library#custom-jest-matchers 169 | type DomTestingLibraryType = { 170 | toBeInTheDOM(): void, 171 | toHaveTextContent(content: string): void, 172 | toHaveAttribute(name: string, expectedValue?: string): void 173 | }; 174 | 175 | // Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers 176 | type JestJQueryMatchersType = { 177 | toExist(): void, 178 | toHaveLength(len: number): void, 179 | toHaveId(id: string): void, 180 | toHaveClass(className: string): void, 181 | toHaveTag(tag: string): void, 182 | toHaveAttr(key: string, val?: any): void, 183 | toHaveProp(key: string, val?: any): void, 184 | toHaveText(text: string | RegExp): void, 185 | toHaveData(key: string, val?: any): void, 186 | toHaveValue(val: any): void, 187 | toHaveCss(css: {[key: string]: any}): void, 188 | toBeChecked(): void, 189 | toBeDisabled(): void, 190 | toBeEmpty(): void, 191 | toBeHidden(): void, 192 | toBeSelected(): void, 193 | toBeVisible(): void, 194 | toBeFocused(): void, 195 | toBeInDom(): void, 196 | toBeMatchedBy(sel: string): void, 197 | toHaveDescendant(sel: string): void, 198 | toHaveDescendantWithText(sel: string, text: string | RegExp): void 199 | }; 200 | 201 | 202 | // Jest Extended Matchers: https://github.com/jest-community/jest-extended 203 | type JestExtendedMatchersType = { 204 | /** 205 | * Note: Currently unimplemented 206 | * Passing assertion 207 | * 208 | * @param {String} message 209 | */ 210 | // pass(message: string): void; 211 | 212 | /** 213 | * Note: Currently unimplemented 214 | * Failing assertion 215 | * 216 | * @param {String} message 217 | */ 218 | // fail(message: string): void; 219 | 220 | /** 221 | * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. 222 | */ 223 | toBeEmpty(): void; 224 | 225 | /** 226 | * Use .toBeOneOf when checking if a value is a member of a given Array. 227 | * @param {Array.<*>} members 228 | */ 229 | toBeOneOf(members: any[]): void; 230 | 231 | /** 232 | * Use `.toBeNil` when checking a value is `null` or `undefined`. 233 | */ 234 | toBeNil(): void; 235 | 236 | /** 237 | * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. 238 | * @param {Function} predicate 239 | */ 240 | toSatisfy(predicate: (n: any) => boolean): void; 241 | 242 | /** 243 | * Use `.toBeArray` when checking if a value is an `Array`. 244 | */ 245 | toBeArray(): void; 246 | 247 | /** 248 | * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. 249 | * @param {Number} x 250 | */ 251 | toBeArrayOfSize(x: number): void; 252 | 253 | /** 254 | * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. 255 | * @param {Array.<*>} members 256 | */ 257 | toIncludeAllMembers(members: any[]): void; 258 | 259 | /** 260 | * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. 261 | * @param {Array.<*>} members 262 | */ 263 | toIncludeAnyMembers(members: any[]): void; 264 | 265 | /** 266 | * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. 267 | * @param {Function} predicate 268 | */ 269 | toSatisfyAll(predicate: (n: any) => boolean): void; 270 | 271 | /** 272 | * Use `.toBeBoolean` when checking if a value is a `Boolean`. 273 | */ 274 | toBeBoolean(): void; 275 | 276 | /** 277 | * Use `.toBeTrue` when checking a value is equal (===) to `true`. 278 | */ 279 | toBeTrue(): void; 280 | 281 | /** 282 | * Use `.toBeFalse` when checking a value is equal (===) to `false`. 283 | */ 284 | toBeFalse(): void; 285 | 286 | /** 287 | * Use .toBeDate when checking if a value is a Date. 288 | */ 289 | toBeDate(): void; 290 | 291 | /** 292 | * Use `.toBeFunction` when checking if a value is a `Function`. 293 | */ 294 | toBeFunction(): void; 295 | 296 | /** 297 | * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. 298 | * 299 | * Note: Required Jest version >22 300 | * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same 301 | * 302 | * @param {Mock} mock 303 | */ 304 | toHaveBeenCalledBefore(mock: JestMockFn): void; 305 | 306 | /** 307 | * Use `.toBeNumber` when checking if a value is a `Number`. 308 | */ 309 | toBeNumber(): void; 310 | 311 | /** 312 | * Use `.toBeNaN` when checking a value is `NaN`. 313 | */ 314 | toBeNaN(): void; 315 | 316 | /** 317 | * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. 318 | */ 319 | toBeFinite(): void; 320 | 321 | /** 322 | * Use `.toBePositive` when checking if a value is a positive `Number`. 323 | */ 324 | toBePositive(): void; 325 | 326 | /** 327 | * Use `.toBeNegative` when checking if a value is a negative `Number`. 328 | */ 329 | toBeNegative(): void; 330 | 331 | /** 332 | * Use `.toBeEven` when checking if a value is an even `Number`. 333 | */ 334 | toBeEven(): void; 335 | 336 | /** 337 | * Use `.toBeOdd` when checking if a value is an odd `Number`. 338 | */ 339 | toBeOdd(): void; 340 | 341 | /** 342 | * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). 343 | * 344 | * @param {Number} start 345 | * @param {Number} end 346 | */ 347 | toBeWithin(start: number, end: number): void; 348 | 349 | /** 350 | * Use `.toBeObject` when checking if a value is an `Object`. 351 | */ 352 | toBeObject(): void; 353 | 354 | /** 355 | * Use `.toContainKey` when checking if an object contains the provided key. 356 | * 357 | * @param {String} key 358 | */ 359 | toContainKey(key: string): void; 360 | 361 | /** 362 | * Use `.toContainKeys` when checking if an object has all of the provided keys. 363 | * 364 | * @param {Array.} keys 365 | */ 366 | toContainKeys(keys: string[]): void; 367 | 368 | /** 369 | * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. 370 | * 371 | * @param {Array.} keys 372 | */ 373 | toContainAllKeys(keys: string[]): void; 374 | 375 | /** 376 | * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. 377 | * 378 | * @param {Array.} keys 379 | */ 380 | toContainAnyKeys(keys: string[]): void; 381 | 382 | /** 383 | * Use `.toContainValue` when checking if an object contains the provided value. 384 | * 385 | * @param {*} value 386 | */ 387 | toContainValue(value: any): void; 388 | 389 | /** 390 | * Use `.toContainValues` when checking if an object contains all of the provided values. 391 | * 392 | * @param {Array.<*>} values 393 | */ 394 | toContainValues(values: any[]): void; 395 | 396 | /** 397 | * Use `.toContainAllValues` when checking if an object only contains all of the provided values. 398 | * 399 | * @param {Array.<*>} values 400 | */ 401 | toContainAllValues(values: any[]): void; 402 | 403 | /** 404 | * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. 405 | * 406 | * @param {Array.<*>} values 407 | */ 408 | toContainAnyValues(values: any[]): void; 409 | 410 | /** 411 | * Use `.toContainEntry` when checking if an object contains the provided entry. 412 | * 413 | * @param {Array.} entry 414 | */ 415 | toContainEntry(entry: [string, string]): void; 416 | 417 | /** 418 | * Use `.toContainEntries` when checking if an object contains all of the provided entries. 419 | * 420 | * @param {Array.>} entries 421 | */ 422 | toContainEntries(entries: [string, string][]): void; 423 | 424 | /** 425 | * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. 426 | * 427 | * @param {Array.>} entries 428 | */ 429 | toContainAllEntries(entries: [string, string][]): void; 430 | 431 | /** 432 | * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. 433 | * 434 | * @param {Array.>} entries 435 | */ 436 | toContainAnyEntries(entries: [string, string][]): void; 437 | 438 | /** 439 | * Use `.toBeExtensible` when checking if an object is extensible. 440 | */ 441 | toBeExtensible(): void; 442 | 443 | /** 444 | * Use `.toBeFrozen` when checking if an object is frozen. 445 | */ 446 | toBeFrozen(): void; 447 | 448 | /** 449 | * Use `.toBeSealed` when checking if an object is sealed. 450 | */ 451 | toBeSealed(): void; 452 | 453 | /** 454 | * Use `.toBeString` when checking if a value is a `String`. 455 | */ 456 | toBeString(): void; 457 | 458 | /** 459 | * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. 460 | * 461 | * @param {String} string 462 | */ 463 | toEqualCaseInsensitive(string: string): void; 464 | 465 | /** 466 | * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. 467 | * 468 | * @param {String} prefix 469 | */ 470 | toStartWith(prefix: string): void; 471 | 472 | /** 473 | * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. 474 | * 475 | * @param {String} suffix 476 | */ 477 | toEndWith(suffix: string): void; 478 | 479 | /** 480 | * Use `.toInclude` when checking if a `String` includes the given `String` substring. 481 | * 482 | * @param {String} substring 483 | */ 484 | toInclude(substring: string): void; 485 | 486 | /** 487 | * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. 488 | * 489 | * @param {String} substring 490 | * @param {Number} times 491 | */ 492 | toIncludeRepeated(substring: string, times: number): void; 493 | 494 | /** 495 | * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. 496 | * 497 | * @param {Array.} substring 498 | */ 499 | toIncludeMultiple(substring: string[]): void; 500 | }; 501 | 502 | interface JestExpectType { 503 | not: JestExpectType & EnzymeMatchersType & DomTestingLibraryType & JestJQueryMatchersType & JestExtendedMatchersType, 504 | /** 505 | * If you have a mock function, you can use .lastCalledWith to test what 506 | * arguments it was last called with. 507 | */ 508 | lastCalledWith(...args: Array): void, 509 | /** 510 | * toBe just checks that a value is what you expect. It uses === to check 511 | * strict equality. 512 | */ 513 | toBe(value: any): void, 514 | /** 515 | * Use .toBeCalledWith to ensure that a mock function was called with 516 | * specific arguments. 517 | */ 518 | toBeCalledWith(...args: Array): void, 519 | /** 520 | * Using exact equality with floating point numbers is a bad idea. Rounding 521 | * means that intuitive things fail. 522 | */ 523 | toBeCloseTo(num: number, delta: any): void, 524 | /** 525 | * Use .toBeDefined to check that a variable is not undefined. 526 | */ 527 | toBeDefined(): void, 528 | /** 529 | * Use .toBeFalsy when you don't care what a value is, you just want to 530 | * ensure a value is false in a boolean context. 531 | */ 532 | toBeFalsy(): void, 533 | /** 534 | * To compare floating point numbers, you can use toBeGreaterThan. 535 | */ 536 | toBeGreaterThan(number: number): void, 537 | /** 538 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 539 | */ 540 | toBeGreaterThanOrEqual(number: number): void, 541 | /** 542 | * To compare floating point numbers, you can use toBeLessThan. 543 | */ 544 | toBeLessThan(number: number): void, 545 | /** 546 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 547 | */ 548 | toBeLessThanOrEqual(number: number): void, 549 | /** 550 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 551 | * class. 552 | */ 553 | toBeInstanceOf(cls: Class<*>): void, 554 | /** 555 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 556 | * nicer. 557 | */ 558 | toBeNull(): void, 559 | /** 560 | * Use .toBeTruthy when you don't care what a value is, you just want to 561 | * ensure a value is true in a boolean context. 562 | */ 563 | toBeTruthy(): void, 564 | /** 565 | * Use .toBeUndefined to check that a variable is undefined. 566 | */ 567 | toBeUndefined(): void, 568 | /** 569 | * Use .toContain when you want to check that an item is in a list. For 570 | * testing the items in the list, this uses ===, a strict equality check. 571 | */ 572 | toContain(item: any): void, 573 | /** 574 | * Use .toContainEqual when you want to check that an item is in a list. For 575 | * testing the items in the list, this matcher recursively checks the 576 | * equality of all fields, rather than checking for object identity. 577 | */ 578 | toContainEqual(item: any): void, 579 | /** 580 | * Use .toEqual when you want to check that two objects have the same value. 581 | * This matcher recursively checks the equality of all fields, rather than 582 | * checking for object identity. 583 | */ 584 | toEqual(value: any): void, 585 | /** 586 | * Use .toHaveBeenCalled to ensure that a mock function got called. 587 | */ 588 | toHaveBeenCalled(): void, 589 | toBeCalled(): void; 590 | /** 591 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 592 | * number of times. 593 | */ 594 | toHaveBeenCalledTimes(number: number): void, 595 | toBeCalledTimes(number: number): void; 596 | /** 597 | * 598 | */ 599 | toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; 600 | nthCalledWith(nthCall: number, ...args: Array): void; 601 | /** 602 | * 603 | */ 604 | toHaveReturned(): void; 605 | toReturn(): void; 606 | /** 607 | * 608 | */ 609 | toHaveReturnedTimes(number: number): void; 610 | toReturnTimes(number: number): void; 611 | /** 612 | * 613 | */ 614 | toHaveReturnedWith(value: any): void; 615 | toReturnWith(value: any): void; 616 | /** 617 | * 618 | */ 619 | toHaveLastReturnedWith(value: any): void; 620 | lastReturnedWith(value: any): void; 621 | /** 622 | * 623 | */ 624 | toHaveNthReturnedWith(nthCall: number, value: any): void; 625 | nthReturnedWith(nthCall: number, value: any): void; 626 | /** 627 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 628 | * specific arguments. 629 | */ 630 | toHaveBeenCalledWith(...args: Array): void, 631 | toBeCalledWith(...args: Array): void, 632 | /** 633 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 634 | * with specific arguments. 635 | */ 636 | toHaveBeenLastCalledWith(...args: Array): void, 637 | lastCalledWith(...args: Array): void, 638 | /** 639 | * Check that an object has a .length property and it is set to a certain 640 | * numeric value. 641 | */ 642 | toHaveLength(number: number): void, 643 | /** 644 | * 645 | */ 646 | toHaveProperty(propPath: string, value?: any): void, 647 | /** 648 | * Use .toMatch to check that a string matches a regular expression or string. 649 | */ 650 | toMatch(regexpOrString: RegExp | string): void, 651 | /** 652 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 653 | */ 654 | toMatchObject(object: Object | Array): void, 655 | /** 656 | * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. 657 | */ 658 | toStrictEqual(value: any): void, 659 | /** 660 | * This ensures that an Object matches the most recent snapshot. 661 | */ 662 | toMatchSnapshot(propertyMatchers?: {[key: string]: JestAsymmetricEqualityType}, name?: string): void, 663 | /** 664 | * This ensures that an Object matches the most recent snapshot. 665 | */ 666 | toMatchSnapshot(name: string): void, 667 | /** 668 | * Use .toThrow to test that a function throws when it is called. 669 | * If you want to test that a specific error gets thrown, you can provide an 670 | * argument to toThrow. The argument can be a string for the error message, 671 | * a class for the error, or a regex that should match the error. 672 | * 673 | * Alias: .toThrowError 674 | */ 675 | toThrow(message?: string | Error | Class | RegExp): void, 676 | toThrowError(message?: string | Error | Class | RegExp): void, 677 | /** 678 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 679 | * matching the most recent snapshot when it is called. 680 | */ 681 | toThrowErrorMatchingSnapshot(): void 682 | } 683 | 684 | type JestObjectType = { 685 | /** 686 | * Disables automatic mocking in the module loader. 687 | * 688 | * After this method is called, all `require()`s will return the real 689 | * versions of each module (rather than a mocked version). 690 | */ 691 | disableAutomock(): JestObjectType, 692 | /** 693 | * An un-hoisted version of disableAutomock 694 | */ 695 | autoMockOff(): JestObjectType, 696 | /** 697 | * Enables automatic mocking in the module loader. 698 | */ 699 | enableAutomock(): JestObjectType, 700 | /** 701 | * An un-hoisted version of enableAutomock 702 | */ 703 | autoMockOn(): JestObjectType, 704 | /** 705 | * Clears the mock.calls and mock.instances properties of all mocks. 706 | * Equivalent to calling .mockClear() on every mocked function. 707 | */ 708 | clearAllMocks(): JestObjectType, 709 | /** 710 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 711 | * mocked function. 712 | */ 713 | resetAllMocks(): JestObjectType, 714 | /** 715 | * Restores all mocks back to their original value. 716 | */ 717 | restoreAllMocks(): JestObjectType, 718 | /** 719 | * Removes any pending timers from the timer system. 720 | */ 721 | clearAllTimers(): void, 722 | /** 723 | * The same as `mock` but not moved to the top of the expectation by 724 | * babel-jest. 725 | */ 726 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 727 | /** 728 | * The same as `unmock` but not moved to the top of the expectation by 729 | * babel-jest. 730 | */ 731 | dontMock(moduleName: string): JestObjectType, 732 | /** 733 | * Returns a new, unused mock function. Optionally takes a mock 734 | * implementation. 735 | */ 736 | fn, TReturn>( 737 | implementation?: (...args: TArguments) => TReturn 738 | ): JestMockFn, 739 | /** 740 | * Determines if the given function is a mocked function. 741 | */ 742 | isMockFunction(fn: Function): boolean, 743 | /** 744 | * Given the name of a module, use the automatic mocking system to generate a 745 | * mocked version of the module for you. 746 | */ 747 | genMockFromModule(moduleName: string): any, 748 | /** 749 | * Mocks a module with an auto-mocked version when it is being required. 750 | * 751 | * The second argument can be used to specify an explicit module factory that 752 | * is being run instead of using Jest's automocking feature. 753 | * 754 | * The third argument can be used to create virtual mocks -- mocks of modules 755 | * that don't exist anywhere in the system. 756 | */ 757 | mock( 758 | moduleName: string, 759 | moduleFactory?: any, 760 | options?: Object 761 | ): JestObjectType, 762 | /** 763 | * Returns the actual module instead of a mock, bypassing all checks on 764 | * whether the module should receive a mock implementation or not. 765 | */ 766 | requireActual(moduleName: string): any, 767 | /** 768 | * Returns a mock module instead of the actual module, bypassing all checks 769 | * on whether the module should be required normally or not. 770 | */ 771 | requireMock(moduleName: string): any, 772 | /** 773 | * Resets the module registry - the cache of all required modules. This is 774 | * useful to isolate modules where local state might conflict between tests. 775 | */ 776 | resetModules(): JestObjectType, 777 | /** 778 | * Exhausts the micro-task queue (usually interfaced in node via 779 | * process.nextTick). 780 | */ 781 | runAllTicks(): void, 782 | /** 783 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 784 | * setInterval(), and setImmediate()). 785 | */ 786 | runAllTimers(): void, 787 | /** 788 | * Exhausts all tasks queued by setImmediate(). 789 | */ 790 | runAllImmediates(): void, 791 | /** 792 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 793 | * or setInterval() and setImmediate()). 794 | */ 795 | advanceTimersByTime(msToRun: number): void, 796 | /** 797 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 798 | * or setInterval() and setImmediate()). 799 | * 800 | * Renamed to `advanceTimersByTime`. 801 | */ 802 | runTimersToTime(msToRun: number): void, 803 | /** 804 | * Executes only the macro-tasks that are currently pending (i.e., only the 805 | * tasks that have been queued by setTimeout() or setInterval() up to this 806 | * point) 807 | */ 808 | runOnlyPendingTimers(): void, 809 | /** 810 | * Explicitly supplies the mock object that the module system should return 811 | * for the specified module. Note: It is recommended to use jest.mock() 812 | * instead. 813 | */ 814 | setMock(moduleName: string, moduleExports: any): JestObjectType, 815 | /** 816 | * Indicates that the module system should never return a mocked version of 817 | * the specified module from require() (e.g. that it should always return the 818 | * real module). 819 | */ 820 | unmock(moduleName: string): JestObjectType, 821 | /** 822 | * Instructs Jest to use fake versions of the standard timer functions 823 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 824 | * setImmediate and clearImmediate). 825 | */ 826 | useFakeTimers(): JestObjectType, 827 | /** 828 | * Instructs Jest to use the real versions of the standard timer functions. 829 | */ 830 | useRealTimers(): JestObjectType, 831 | /** 832 | * Creates a mock function similar to jest.fn but also tracks calls to 833 | * object[methodName]. 834 | */ 835 | spyOn(object: Object, methodName: string, accessType?: "get" | "set"): JestMockFn, 836 | /** 837 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 838 | * Note: The default timeout interval is 5 seconds if this method is not called. 839 | */ 840 | setTimeout(timeout: number): JestObjectType 841 | }; 842 | 843 | type JestSpyType = { 844 | calls: JestCallsType 845 | }; 846 | 847 | /** Runs this function after every test inside this context */ 848 | declare function afterEach( 849 | fn: (done: () => void) => ?Promise, 850 | timeout?: number 851 | ): void; 852 | /** Runs this function before every test inside this context */ 853 | declare function beforeEach( 854 | fn: (done: () => void) => ?Promise, 855 | timeout?: number 856 | ): void; 857 | /** Runs this function after all tests have finished inside this context */ 858 | declare function afterAll( 859 | fn: (done: () => void) => ?Promise, 860 | timeout?: number 861 | ): void; 862 | /** Runs this function before any tests have started inside this context */ 863 | declare function beforeAll( 864 | fn: (done: () => void) => ?Promise, 865 | timeout?: number 866 | ): void; 867 | 868 | /** A context for grouping tests together */ 869 | declare var describe: { 870 | /** 871 | * Creates a block that groups together several related tests in one "test suite" 872 | */ 873 | (name: JestTestName, fn: () => void): void, 874 | 875 | /** 876 | * Only run this describe block 877 | */ 878 | only(name: JestTestName, fn: () => void): void, 879 | 880 | /** 881 | * Skip running this describe block 882 | */ 883 | skip(name: JestTestName, fn: () => void): void 884 | }; 885 | 886 | /** An individual test unit */ 887 | declare var it: { 888 | /** 889 | * An individual test unit 890 | * 891 | * @param {JestTestName} Name of Test 892 | * @param {Function} Test 893 | * @param {number} Timeout for the test, in milliseconds. 894 | */ 895 | ( 896 | name: JestTestName, 897 | fn?: (done: () => void) => ?Promise, 898 | timeout?: number 899 | ): void, 900 | /** 901 | * Only run this test 902 | * 903 | * @param {JestTestName} Name of Test 904 | * @param {Function} Test 905 | * @param {number} Timeout for the test, in milliseconds. 906 | */ 907 | only( 908 | name: JestTestName, 909 | fn?: (done: () => void) => ?Promise, 910 | timeout?: number 911 | ): void, 912 | /** 913 | * Skip running this test 914 | * 915 | * @param {JestTestName} Name of Test 916 | * @param {Function} Test 917 | * @param {number} Timeout for the test, in milliseconds. 918 | */ 919 | skip( 920 | name: JestTestName, 921 | fn?: (done: () => void) => ?Promise, 922 | timeout?: number 923 | ): void, 924 | /** 925 | * Run the test concurrently 926 | * 927 | * @param {JestTestName} Name of Test 928 | * @param {Function} Test 929 | * @param {number} Timeout for the test, in milliseconds. 930 | */ 931 | concurrent( 932 | name: JestTestName, 933 | fn?: (done: () => void) => ?Promise, 934 | timeout?: number 935 | ): void 936 | }; 937 | declare function fit( 938 | name: JestTestName, 939 | fn: (done: () => void) => ?Promise, 940 | timeout?: number 941 | ): void; 942 | /** An individual test unit */ 943 | declare var test: typeof it; 944 | /** A disabled group of tests */ 945 | declare var xdescribe: typeof describe; 946 | /** A focused group of tests */ 947 | declare var fdescribe: typeof describe; 948 | /** A disabled individual test */ 949 | declare var xit: typeof it; 950 | /** A disabled individual test */ 951 | declare var xtest: typeof it; 952 | 953 | type JestPrettyFormatColors = { 954 | comment: { close: string, open: string }, 955 | content: { close: string, open: string }, 956 | prop: { close: string, open: string }, 957 | tag: { close: string, open: string }, 958 | value: { close: string, open: string }, 959 | }; 960 | 961 | type JestPrettyFormatIndent = string => string; 962 | type JestPrettyFormatRefs = Array; 963 | type JestPrettyFormatPrint = any => string; 964 | type JestPrettyFormatStringOrNull = string | null; 965 | 966 | type JestPrettyFormatOptions = {| 967 | callToJSON: boolean, 968 | edgeSpacing: string, 969 | escapeRegex: boolean, 970 | highlight: boolean, 971 | indent: number, 972 | maxDepth: number, 973 | min: boolean, 974 | plugins: JestPrettyFormatPlugins, 975 | printFunctionName: boolean, 976 | spacing: string, 977 | theme: {| 978 | comment: string, 979 | content: string, 980 | prop: string, 981 | tag: string, 982 | value: string, 983 | |}, 984 | |}; 985 | 986 | type JestPrettyFormatPlugin = { 987 | print: ( 988 | val: any, 989 | serialize: JestPrettyFormatPrint, 990 | indent: JestPrettyFormatIndent, 991 | opts: JestPrettyFormatOptions, 992 | colors: JestPrettyFormatColors, 993 | ) => string, 994 | test: any => boolean, 995 | }; 996 | 997 | type JestPrettyFormatPlugins = Array; 998 | 999 | /** The expect function is used every time you want to test a value */ 1000 | declare var expect: { 1001 | /** The object that you want to make assertions against */ 1002 | (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType & DomTestingLibraryType & JestJQueryMatchersType & JestExtendedMatchersType, 1003 | /** Add additional Jasmine matchers to Jest's roster */ 1004 | extend(matchers: { [name: string]: JestMatcher }): void, 1005 | /** Add a module that formats application-specific data structures. */ 1006 | addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, 1007 | assertions(expectedAssertions: number): void, 1008 | hasAssertions(): void, 1009 | any(value: mixed): JestAsymmetricEqualityType, 1010 | anything(): any, 1011 | arrayContaining(value: Array): Array, 1012 | objectContaining(value: Object): Object, 1013 | /** Matches any received string that contains the exact expected string. */ 1014 | stringContaining(value: string): string, 1015 | stringMatching(value: string | RegExp): string, 1016 | not: { 1017 | arrayContaining: (value: $ReadOnlyArray) => Array, 1018 | objectContaining: (value: {}) => Object, 1019 | stringContaining: (value: string) => string, 1020 | stringMatching: (value: string | RegExp) => string, 1021 | }, 1022 | }; 1023 | 1024 | // TODO handle return type 1025 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 1026 | declare function spyOn(value: mixed, method: string): Object; 1027 | 1028 | /** Holds all functions related to manipulating test runner */ 1029 | declare var jest: JestObjectType; 1030 | 1031 | /** 1032 | * The global Jasmine object, this is generally not exposed as the public API, 1033 | * using features inside here could break in later versions of Jest. 1034 | */ 1035 | declare var jasmine: { 1036 | DEFAULT_TIMEOUT_INTERVAL: number, 1037 | any(value: mixed): JestAsymmetricEqualityType, 1038 | anything(): any, 1039 | arrayContaining(value: Array): Array, 1040 | clock(): JestClockType, 1041 | createSpy(name: string): JestSpyType, 1042 | createSpyObj( 1043 | baseName: string, 1044 | methodNames: Array 1045 | ): { [methodName: string]: JestSpyType }, 1046 | objectContaining(value: Object): Object, 1047 | stringMatching(value: string): string 1048 | }; 1049 | -------------------------------------------------------------------------------- /docs/docs.js: -------------------------------------------------------------------------------- 1 | !function(e){var t=window.webpackHotUpdate;window.webpackHotUpdate=function(e,n){!function(e,t){if(!w[e]||!b[e])return;for(var n in b[e]=!1,t)Object.prototype.hasOwnProperty.call(t,n)&&(m[n]=t[n]);0==--v&&0===y&&x()}(e,n),t&&t(e,n)};var n,r=!0,o="870ddbead90dea800006",a=1e4,i={},l=[],u=[];function c(e){var t=S[e];if(!t)return T;var r=function(r){return t.hot.active?(S[r]?-1===S[r].parents.indexOf(e)&&S[r].parents.push(e):(l=[e],n=r),-1===t.children.indexOf(r)&&t.children.push(r)):(console.warn("[HMR] unexpected require("+r+") from disposed module "+e),l=[]),T(r)},o=function(e){return{configurable:!0,enumerable:!0,get:function(){return T[e]},set:function(t){T[e]=t}}};for(var a in T)Object.prototype.hasOwnProperty.call(T,a)&&"e"!==a&&"t"!==a&&Object.defineProperty(r,a,o(a));return r.e=function(e){return"ready"===f&&d("prepare"),y++,T.e(e).then(t,function(e){throw t(),e});function t(){y--,"prepare"===f&&(g[e]||k(e),0===y&&0===v&&x())}},r.t=function(e,t){return 1&t&&(e=r(e)),T.t(e,-2&t)},r}var s=[],f="idle";function d(e){f=e;for(var t=0;t0;){var o=r.pop(),a=o.id,i=o.chain;if((u=S[a])&&!u.hot._selfAccepted){if(u.hot._selfDeclined)return{type:"self-declined",chain:i,moduleId:a};if(u.hot._main)return{type:"unaccepted",chain:i,moduleId:a};for(var l=0;l ")),k.type){case"self-declined":t.onDeclined&&t.onDeclined(k),t.ignoreDeclined||(x=new Error("Aborted because of self decline: "+k.moduleId+N));break;case"declined":t.onDeclined&&t.onDeclined(k),t.ignoreDeclined||(x=new Error("Aborted because of declined dependency: "+k.moduleId+" in "+k.parentId+N));break;case"unaccepted":t.onUnaccepted&&t.onUnaccepted(k),t.ignoreUnaccepted||(x=new Error("Aborted because "+c+" is not accepted"+N));break;case"accepted":t.onAccepted&&t.onAccepted(k),C=!0;break;case"disposed":t.onDisposed&&t.onDisposed(k),P=!0;break;default:throw new Error("Unexception type "+k.type)}if(x)return d("abort"),Promise.reject(x);if(C)for(c in g[c]=m[c],p(y,k.outdatedModules),k.outdatedDependencies)Object.prototype.hasOwnProperty.call(k.outdatedDependencies,c)&&(v[c]||(v[c]=[]),p(v[c],k.outdatedDependencies[c]));P&&(p(y,[k.moduleId]),g[c]=b)}var O,j=[];for(r=0;r0;)if(c=M.pop(),u=S[c]){var I={},D=u.hot._disposeHandlers;for(a=0;a=0&&F.parents.splice(O,1))}}for(c in v)if(Object.prototype.hasOwnProperty.call(v,c)&&(u=S[c]))for(R=v[c],a=0;a=0&&u.children.splice(O,1);for(c in d("apply"),o=h,g)Object.prototype.hasOwnProperty.call(g,c)&&(e[c]=g[c]);var U=null;for(c in v)if(Object.prototype.hasOwnProperty.call(v,c)&&(u=S[c])){R=v[c];var L=[];for(r=0;r=0&&t._disposeHandlers.splice(n,1)},check:E,apply:C,status:function(e){if(!e)return f;s.push(e)},addStatusHandler:function(e){s.push(e)},removeStatusHandler:function(e){var t=s.indexOf(e);t>=0&&s.splice(t,1)},data:i[e]};return n=void 0,t}(t),parents:(u=l,l=[],u),children:[]};return e[t].call(r.exports,r,r.exports,c(t)),r.l=!0,r.exports}T.m=e,T.c=S,T.d=function(e,t,n){T.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},T.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},T.t=function(e,t){if(1&t&&(e=T(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(T.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)T.d(n,r,function(t){return e[t]}.bind(null,r));return n},T.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return T.d(t,"a",t),t},T.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},T.p="",T.h=function(){return o},c("./demo/index.js")(T.s="./demo/index.js")}({"./demo/CodeExample.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n6?r._randomRemove():"Add"===t?r._randomAdd():"Remove"===t?r._randomRemove():r._randomAddRemove()},r._randomAdd=function(){var e=r.state.items,t=r._getRandomInt(e.length);r.setState({items:[].concat(u(e.slice(0,t)),[r._generateNewItem()],u(e.slice(t)))})},r._randomRemove=function(){var e=r.state.items,t=r._getRandomInt(e.length);r.setState({items:[].concat(u(e.slice(0,t)),u(e.slice(t+1)))})},r._randomAddRemove=function(){var e=r.state.items,t=r._getRandomInt(e.length),n=[].concat(u(e.slice(0,t)),u(e.slice(t+1))),o=r._getRandomInt(n.length);r.setState({items:[].concat(u(n.slice(0,o)),[r._generateNewItem()],u(n.slice(o)))})},r._getRandomInt=function(e){return Math.floor(Math.random()*Math.floor(e))},r._generateNewItem=function(){return{name:s++,bgc:f[s%10]}},c(r,n)}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,a.default.PureComponent),o(t,[{key:"render",value:function(){var e=this.state.items;return a.default.createElement("div",{className:"example-root"},a.default.createElement("div",{onClick:this._handleRandomAction,className:"example-toggle"},"Add/Remove item"),a.default.createElement(i.default,null,e.map(function(e){return a.default.createElement("div",{key:e.name,style:r({},p,{backgroundColor:e.bgc})},a.default.createElement("p",{style:m},e.name))})))}}]),t}();t.default=h},"./demo/index.js":function(e,t,n){"use strict";var r=function(){function e(e,t){for(var n=0;n=180&&!t&&e.setState({floatingNav:!0}),window.pageYOffset<180&&t&&e.setState({floatingNav:!1})},window.innerWidth<800&&this.setState({narrowScreen:!0})}},{key:"render",value:function(){var e=this.state,t=e.floatingNav,n=e.narrowScreen,r=n?"floating-right":"floating-right right-margin";return o.default.createElement(o.default.Fragment,null,o.default.createElement("div",{className:"floating-nav-fixed"},o.default.createElement(i.Animate,{show:t},o.default.createElement("div",{className:"floating-nav"},o.default.createElement("ul",{className:r},o.default.createElement("li",null,o.default.createElement("a",{href:"#",onClick:this.scrollToAnimateSection},"Animate")),o.default.createElement("li",null,o.default.createElement("a",{href:"#",onClick:this.scrollToAnimateGroupSection},"AnimateGroup")),o.default.createElement("li",null,o.default.createElement("a",{href:"#",onClick:this.scrollToGithubSection},"Github"))),n?null:o.default.createElement("div",{className:"floating-left"},"React Animate Mount")))),o.default.createElement("div",{className:"root"},o.default.createElement(i.Animate,{appear:!0,duration:750,show:!0},o.default.createElement("h1",{className:"header"},"React Animate Mount"),o.default.createElement("h3",{className:"header-sub"},"Simple and light component for animating mount and unmount")),o.default.createElement("ul",{className:"nav"},o.default.createElement("li",null,o.default.createElement("a",{href:"#",onClick:this.scrollToAnimateSection},"Animate")),o.default.createElement("li",null,o.default.createElement("a",{href:"#",onClick:this.scrollToAnimateGroupSection},"AnimateGroup")),o.default.createElement("li",null,o.default.createElement("a",{href:"#",onClick:this.scrollToGithubSection},"Github"))),o.default.createElement("section",{className:"section",id:"animate",ref:this._animateSectionRef},o.default.createElement("h2",{className:"section-header"},"< Animate />"),o.default.createElement("p",{className:"section-text"},"SlideUp / SlideDown component (animate height 0 and 'auto', like this page's floating nav)"),o.default.createElement(s.default,{code:o.default.createElement(o.default.Fragment,null,o.default.createElement("div",{className:"code-red"},""),o.default.createElement("div",null,'
'),o.default.createElement("div",null,"

This component slideUp and slideDown

"),o.default.createElement("div",null,"
"),o.default.createElement("div",{className:"code-red"},"
")),example:o.default.createElement(l.default,null)}),o.default.createElement("p",{className:"section-text"},"FadeIn / FadeOut component (animate opacity 0 and 1)"),o.default.createElement(s.default,{code:o.default.createElement(o.default.Fragment,null,o.default.createElement("div",{className:"code-red"},o.default.createElement("span",null,"")),o.default.createElement("div",null,'
'),o.default.createElement("div",null,"

This component fadeIn and fadeOut

"),o.default.createElement("div",null,"
"),o.default.createElement("div",{className:"code-red"},"
")),example:o.default.createElement(u.default,null)}),o.default.createElement("p",{className:"section-text"},"Or simply animateIn on mount (like this page's header) "),o.default.createElement(s.default,{code:o.default.createElement(o.default.Fragment,null,o.default.createElement("div",{className:"code-red"},o.default.createElement("span",null,"")),o.default.createElement("div",null,"

React Animate Mount

"),o.default.createElement("div",null,"

Simple and light component...

"),o.default.createElement("div",{className:"code-red"},"
"))})),o.default.createElement("section",{className:"section",id:"animate-group",ref:this._animateGroupSectionRef},o.default.createElement("h2",{className:"section-header"},"< AnimateGroup />"),o.default.createElement("p",{className:"section-text"},"Animate items when they are added / removed from a list"),o.default.createElement(s.default,{code:o.default.createElement(o.default.Fragment,null,o.default.createElement("div",{className:"code-red"},""),o.default.createElement("div",null," {items.map(item => ()}"),o.default.createElement("div",{className:"code-red"},"")),example:o.default.createElement(c.default,null)})),o.default.createElement("section",{className:"section",id:"github",ref:this._githubSectionRef},o.default.createElement("h2",{className:"section-header"},"Github"),o.default.createElement("a",{href:"https://github.com/MingruiZhang/react-animate-mount",className:"section-text",target:"_blank"},"https://github.com/MingruiZhang/react-animate-mount"))))}}]),t}(),m=document.getElementById("root");a.default.render(o.default.createElement(p,null),m)},"./demo/styles.css":function(e,t){},"./node_modules/fbjs/lib/ExecutionEnvironment.js":function(e,t,n){"use strict";var r=!("undefined"==typeof window||!window.document||!window.document.createElement),o={canUseDOM:r,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:r&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:r&&!!window.screen,isInWorker:!r};e.exports=o},"./node_modules/fbjs/lib/containsNode.js":function(e,t,n){"use strict";var r=n("./node_modules/fbjs/lib/isTextNode.js");e.exports=function e(t,n){return!(!t||!n)&&(t===n||!r(t)&&(r(n)?e(t,n.parentNode):"contains"in t?t.contains(n):!!t.compareDocumentPosition&&!!(16&t.compareDocumentPosition(n))))}},"./node_modules/fbjs/lib/emptyFunction.js":function(e,t,n){"use strict";function r(e){return function(){return e}}var o=function(){};o.thatReturns=r,o.thatReturnsFalse=r(!1),o.thatReturnsTrue=r(!0),o.thatReturnsNull=r(null),o.thatReturnsThis=function(){return this},o.thatReturnsArgument=function(e){return e},e.exports=o},"./node_modules/fbjs/lib/emptyObject.js":function(e,t,n){"use strict";e.exports={}},"./node_modules/fbjs/lib/getActiveElement.js":function(e,t,n){"use strict";e.exports=function(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}},"./node_modules/fbjs/lib/invariant.js":function(e,t,n){"use strict";var r=function(e){};e.exports=function(e,t,n,o,a,i,l,u){if(r(t),!e){var c;if(void 0===t)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var s=[n,o,a,i,l,u],f=0;(c=new Error(t.replace(/%s/g,function(){return s[f++]}))).name="Invariant Violation"}throw c.framesToPop=1,c}}},"./node_modules/fbjs/lib/isNode.js":function(e,t,n){"use strict";e.exports=function(e){var t=(e?e.ownerDocument||e:document).defaultView||window;return!(!e||!("function"==typeof t.Node?e instanceof t.Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}},"./node_modules/fbjs/lib/isTextNode.js":function(e,t,n){"use strict";var r=n("./node_modules/fbjs/lib/isNode.js");e.exports=function(e){return r(e)&&3==e.nodeType}},"./node_modules/fbjs/lib/shallowEqual.js":function(e,t,n){"use strict";var r=Object.prototype.hasOwnProperty;function o(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}e.exports=function(e,t){if(o(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),a=Object.keys(t);if(n.length!==a.length)return!1;for(var i=0;ithis.eventPool.length&&this.eventPool.push(e)}function ke(e){e.eventPool=[],e.getPooled=_e,e.release=Ee}i(we.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=l.thatReturnsTrue)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=l.thatReturnsTrue)},persist:function(){this.isPersistent=l.thatReturnsTrue},isPersistent:l.thatReturnsFalse,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;for(t=0;t=Pe),je=String.fromCharCode(32),Ae={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},Re=!1;function Me(e,t){switch(e){case"keyup":return-1!==Se.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function Ie(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var De=!1;var Fe={eventTypes:Ae,extractEvents:function(e,t,n,r){var o=void 0,a=void 0;if(Te)e:{switch(e){case"compositionstart":o=Ae.compositionStart;break e;case"compositionend":o=Ae.compositionEnd;break e;case"compositionupdate":o=Ae.compositionUpdate;break e}o=void 0}else De?Me(e,n)&&(o=Ae.compositionEnd):"keydown"===e&&229===n.keyCode&&(o=Ae.compositionStart);return o?(Oe&&(De||o!==Ae.compositionStart?o===Ae.compositionEnd&&De&&(a=ve()):(he._root=r,he._startText=ye(),De=!0)),o=xe.getPooled(o,t,n,r),a?o.data=a:null!==(a=Ie(n))&&(o.data=a),ee(o),a=o):a=null,(e=Ne?function(e,t){switch(e){case"compositionend":return Ie(t);case"keypress":return 32!==t.which?null:(Re=!0,je);case"textInput":return(e=t.data)===je&&Re?null:e;default:return null}}(e,n):function(e,t){if(De)return"compositionend"===e||!Te&&Me(e,t)?(e=ve(),he._root=null,he._startText=null,he._fallbackText=null,De=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1