├── .npmignore
├── .gitignore
├── .jshintrc
├── src
├── is-in.js
├── clone-with-classes.js
├── merge-children.js
└── index.js
├── index.html
├── LICENSE
├── package.json
├── demos
├── index.css
└── index.jsx
├── README.md
└── test
└── merge-children.js
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | test
3 | example
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "browser": true,
4 | "globalstrict": true
5 | }
6 |
--------------------------------------------------------------------------------
/src/is-in.js:
--------------------------------------------------------------------------------
1 | import find from 'lodash/collection/find';
2 |
3 | function isIn(item, array) {
4 | return !!find(array, 'key', item.key);
5 | }
6 |
7 | export default isIn;
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Transition Manager for React | Collingo
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/clone-with-classes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classnames from 'classnames/dedupe';
3 |
4 | function getClasses(state) {
5 | let classes = 'add';
6 | switch(state) {
7 | case 'show':
8 | classes += ' show';
9 | break;
10 | case 'shown':
11 | classes += ' show shown';
12 | break;
13 | case 'hide':
14 | classes += ' show shown hide';
15 | break;
16 | }
17 | return classes;
18 | }
19 |
20 | export default function cloneWithClasses(element, state) {
21 | let currentClasses = element.props.className ? element.props.className.split(' ') : [];
22 | let newClasses = classnames.apply(null, currentClasses.concat(getClasses(state)));
23 | const newElement = React.cloneElement(element, {
24 | className: newClasses,
25 | transitionState: state
26 | });
27 | return newElement;
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Nick Collings
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 |
23 |
--------------------------------------------------------------------------------
/src/merge-children.js:
--------------------------------------------------------------------------------
1 | import cloneWithClasses from './clone-with-classes';
2 | import isIn from './is-in';
3 |
4 | function mergeChildren(currentChildren, targetChildren, persisting, targetEntering, targetLeaving) {
5 | let targetIndex = 0;
6 | let currentIndex = 0;
7 | let targetChild = targetChildren[targetIndex];
8 | let currentChild = currentChildren[currentIndex];
9 | let children = [];
10 | while(targetChild || currentChild) {
11 | while(targetChild && !isIn(targetChild, persisting)) {
12 | let state = isIn(targetChild, targetEntering) ? 'show' : 'add';
13 | children.push(cloneWithClasses(targetChild, state));
14 | targetChild = targetChildren[++targetIndex];
15 | }
16 | while(currentChild && !isIn(currentChild, persisting)) {
17 | if(!isIn(currentChild, children)) {
18 | let state = isIn(currentChild, targetLeaving) ? 'hide' : 'remove';
19 | children.push(cloneWithClasses(currentChild, state));
20 | }
21 | currentChild = currentChildren[++currentIndex];
22 | }
23 | if(targetChild) {
24 | children.push(cloneWithClasses(targetChild, 'shown'));
25 | targetChild = targetChildren[++targetIndex];
26 | currentChild = currentChildren[++currentIndex];
27 | }
28 | }
29 | return children;
30 | }
31 |
32 | export default mergeChildren;
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-transition-manager",
3 | "version": "1.3.1",
4 | "description": "A robust transition component for React projects",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "compile": "babel -d ./lib ./src",
8 | "test": "mocha --compilers js:babel/register",
9 | "pages": "browserify -o demos/index.js demos/index.jsx",
10 | "prepublish": "npm run compile"
11 | },
12 | "browserify": {
13 | "transform": [
14 | "babelify"
15 | ]
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/collingo/react-transition-manager.git"
20 | },
21 | "keywords": [
22 | "react-component"
23 | ],
24 | "author": "Nick Collings",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/collingo/react-transition-manager/issues"
28 | },
29 | "homepage": "https://github.com/collingo/react-transition-manager",
30 | "dependencies": {
31 | "classnames": "^2.1.2",
32 | "lodash": "^3.9.3",
33 | "react": "^15.3.2",
34 | "react-dom": "^15.3.2"
35 | },
36 | "devDependencies": {
37 | "babel": "^5.8.38",
38 | "babelify": "^6.4.0",
39 | "browserify": "^10.2.6",
40 | "chai": "^3.0.0",
41 | "mocha": "^2.2.5",
42 | "proxyquire": "^1.5.0",
43 | "sinon": "^1.15.4",
44 | "sinon-chai": "^2.8.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/demos/index.css:
--------------------------------------------------------------------------------
1 | .fade .stage,
2 | .translate .stage {
3 | position: relative;
4 | height: 200px;
5 | width: 200px;
6 | border: 1px solid black;
7 | margin-left: 200px;
8 | }
9 | .fade .page,
10 | .translate .page {
11 | display: block;
12 | position: absolute;
13 | width: 200px;
14 | height:110px;
15 | text-align: center;
16 | padding-top: 90px;
17 | background: #5c5;
18 | }
19 | .translate .page {
20 | transition: transform 1s ease-in-out;
21 | -webkit-transform: translate3d(0,0,0);
22 | transform: translate3d(0,0,0);
23 | -webkit-backface-visibility: hidden; /* Chrome, Safari, Opera */
24 | backface-visibility: hidden;
25 | }
26 | .translate .page-1,
27 | .translate .page-4,
28 | .translate .page-7 {
29 | background: #c55;
30 | }
31 | .translate .page-2,
32 | .translate .page-5,
33 | .translate .page-8 {
34 | background: #55c;
35 | }
36 | .translate .page.add {
37 | -webkit-transform: translate3d(200px,0,0);
38 | transform: translate3d(200px,0,0);
39 | }
40 | .translate .page.show {
41 | -webkit-transform: translate3d(0px,0,0);
42 | transform: translate3d(0px,0,0);
43 | }
44 | .translate .page.hide {
45 | -webkit-transform: translate3d(-200px,0,0);
46 | transform: translate3d(-200px,0,0);
47 | }
48 | .translate.final .stage {
49 | overflow: hidden;
50 | margin: 0;
51 | }
52 | .fade .stage {
53 | margin: 0;
54 | }
55 | .fade .page {
56 | transition: opacity 1s ease-in-out;
57 | }
58 | .fade .page.add {
59 | opacity: 0;
60 | }
61 | .fade .page.show {
62 | transition-delay: 0.5s;
63 | opacity: 1;
64 | }
65 | .fade .page.hide {
66 | transition-delay: 0s;
67 | opacity: 0;
68 | }
69 |
--------------------------------------------------------------------------------
/demos/index.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import TransitionManager from '../src/index';
6 |
7 | const noOfPages = 9;
8 | const Demo = React.createClass({
9 | displayName: "Demo",
10 | getInitialState() {
11 | return {
12 | page: 0
13 | };
14 | },
15 | render() {
16 | const pageSlug = `page-${this.state.page}`;
17 | return (
18 |
19 |
Previous
20 |
Next
21 |
22 | {`Page ${this.state.page}`}
23 |
24 |
25 | );
26 | },
27 | onClickPrev() {
28 | this.setState({
29 | page: (noOfPages + this.state.page - 1) % noOfPages
30 | });
31 | },
32 | onClickNext() {
33 | this.setState({
34 | page: (noOfPages + this.state.page + 1) % noOfPages
35 | });
36 | }
37 | });
38 | const DemoPage = React.createClass({
39 | displayName: "DemoPage",
40 | render() {
41 | return (
42 |
43 |
Transition Manager
44 |
See readme on Github for usage instructions.
45 |
See demos directory in repo for the code behind these demos.
46 |
Demos
47 |
All three demos use the same JSX and only vary in style.
48 |
49 | No styles
50 | Click next/previous quickly and you should see the DOM filling up with new pages. Multiple renders are taking place during this time but transitioning elements are not removed until their transition timeout is complete.
51 |
52 |
53 |
54 | Translate 1
55 | A simple transition in the form of a translate from right to left. Overflow is visible to help visualise the transition.
56 |
57 |
58 |
59 | Translate 2
60 | Overflow hidden to complete the effect.
61 |
62 |
63 |
64 | Fade
65 | Making use of opacity and transition-delay to create a fade effect.
66 |
67 |
68 |
69 | );
70 | }
71 | });
72 |
73 | ReactDOM.render(
74 | ,
75 | document.body
76 | );
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TransitionManager
2 |
3 | A robust transition component for React projects which can handle the stress. It can cope with multiple, simultaneous transitions being queued up in quick succession as well as changes to these part way through.
4 |
5 | ## Why?
6 |
7 | I was looking for a transition component that allows for smooth page changes which can be tied to the browser history. Since the user can navigate the history at any rate they wish, the transition component would need to be able to juggle multiple transitions at a time. It seemed clear that any transitioning elements needed to be cached in some stateful manner in order to keep tabs on them between renders. Unfortunately none of the existing transition components seemed to support this idea, so subsequent renders would not be aware of any transitioning elements and they would be removed prematurely.
8 |
9 | ## How it works
10 |
11 | Inspired by React's own [`ReactCSSTransitionGroup`](https://facebook.github.io/react/docs/animation.html) component, `TransitionManager` allows you to simply declare the children you want to see, leaving the nasty diffing logic to React. Additionally, to cope with transitionends not firing in certain cases, it employs a setTimeout safety net just as the clever [`TimeoutTransitionGroup`](https://github.com/Khan/react-components/blob/master/js/timeout-transition-group.jsx) from [Khan Academy](https://www.khanacademy.org/). It will then keep track of all children in its internal state, whether they are entering, leaving or persisting, whilst adding classes to them to trigger the appropriate css transitions. Children are only removed after the timeout is complete and all timeouts are allowed to run to their conclusion regardless of the number of render calls taking place inbetween.
12 |
13 | ## Transition cycle
14 |
15 | Children will receive the following classes as props at each stage of their transition cycle. Please note, you will need to apply these classes to the children manually.*
16 |
17 | * `add` for new elements
18 | * prepare them for entry transition
19 | * `add show` for entering elements
20 | * `show` is added on the next tick in order to trigger the enter css transition
21 | * `add show hide` for leaving elements
22 | * `hide` is added in order to trigger the leave css transition
23 | * element is removed from the dom after timeout duration
24 |
25 | \* this is subject to change in a future version as this would best be automated by `TransitionManager`.
26 |
27 | ## Usage
28 |
29 | ### Example Parent
30 |
31 | Using a similar api to `TimeoutTransitionGroup`, you need to pass in a duration value in ms for the leave timeouts. All children must have a unique `key` so `TransitionManager` can keep tabs on each child. E.g.
32 |
33 | `app.js`
34 | ```js
35 | import TransitionManager from 'react-transition-manager';
36 | const App = React.createClass({
37 | render() {
38 | let page;
39 | switch(this.props.pageId) {
40 | case 'home':
41 | page = ;
42 | break;
43 | case 'about':
44 | page = ;
45 | break;
46 | case 'contact':
47 | page = ;
48 | break;
49 | }
50 | return (
51 |
52 | {page}
53 |
54 | );
55 | }
56 | });
57 | export default App;
58 | ```
59 |
60 | * all passed props (`id`, `className` etc) will be applied to the rendered dom
61 | * `component` attribute allows for overriding the default `` element type.
62 |
63 | ### Example Child
64 |
65 | The classes are passed down to the child components in the `className` property. These need to be applied during the render method to take affect.
66 |
67 | Additionally the current `transitionState` is also passed as prop in case it's needed in the render logic.
68 |
69 | `home.js`
70 | ```js
71 | var Home = React.createClass({
72 | render: function() {
73 | return (
74 |
75 | Page is currently {this.props.transitionState}
76 |
77 | );
78 | }
79 | });
80 | export default Home;
81 | ```
82 |
83 | ## Demos
84 |
85 | [collingo.com/react-transition-manager](http://collingo.com/react-transition-manager/)
86 |
87 | ## License
88 | [MIT](http://opensource.org/licenses/MIT)
89 |
--------------------------------------------------------------------------------
/test/merge-children.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import chai, {expect} from 'chai';
4 | import sinon from 'sinon';
5 | import sinonChai from 'sinon-chai';
6 | import proxyquire from 'proxyquire';
7 |
8 | chai.use(sinonChai);
9 |
10 | function cloneWithClasses(item, state) {
11 | item.classes = state;
12 | return item;
13 | }
14 |
15 | describe('mergeChildren', () => {
16 |
17 | let sandbox;
18 | let mergeChildren;
19 | let cloneWithClassesSpy;
20 | let child1;
21 | let child2;
22 | let child3;
23 | let child4;
24 | let child5;
25 | let result;
26 |
27 | beforeEach(() => {
28 | sandbox = sinon.sandbox.create();
29 | cloneWithClassesSpy = sandbox.spy(cloneWithClasses);
30 | mergeChildren = proxyquire('../src/merge-children', {
31 | './clone-with-classes': cloneWithClassesSpy
32 | });
33 | child1 = { key: 1 };
34 | child2 = { key: 2 };
35 | child3 = { key: 3 };
36 | child4 = { key: 4 };
37 | child5 = { key: 5 };
38 | });
39 |
40 | afterEach(() => {
41 | sandbox.restore();
42 | });
43 |
44 | describe('when no children', () => {
45 | beforeEach(() => {
46 | result = mergeChildren([], [], []);
47 | });
48 | it('should return an empty array', () => {
49 | expect(Array.isArray(result)).to.be.true;
50 | expect(result.length).to.equal(0);
51 | });
52 | });
53 |
54 | describe('when no changes', () => {
55 | beforeEach(() => {
56 | result = mergeChildren([child1], [child1], [child1]);
57 | });
58 | it('should return same children', () => {
59 | expect(result.length).to.equal(1);
60 | expect(result[0].key).to.equal(child1.key);
61 | });
62 | it('should add "shown" classes to child', () => {
63 | expect(result[0].classes).to.equal("shown");
64 | });
65 | });
66 |
67 | describe('when adding', () => {
68 | beforeEach(() => {
69 | result = mergeChildren([child1], [child1, child2], [child1]);
70 | });
71 | it('should return two children', () => {
72 | expect(result.length).to.equal(2);
73 | });
74 | it('should be in the correct order', () => {
75 | expect(result[0].key).to.equal(child1.key);
76 | expect(result[1].key).to.equal(child2.key);
77 | });
78 | it('should add "add" classes to new child', () => {
79 | expect(result[1].classes).to.equal("add");
80 | });
81 | });
82 |
83 | describe('when removing', () => {
84 | beforeEach(() => {
85 | result = mergeChildren([child1], [], []);
86 | });
87 | it('should return same children', () => {
88 | expect(result.length).to.equal(1);
89 | expect(result[0].key).to.equal(child1.key);
90 | });
91 | it('should add "hide" classes to old child', () => {
92 | expect(result[0].classes).to.equal("hide");
93 | });
94 | });
95 |
96 | describe('when switching', () => {
97 | beforeEach(() => {
98 | result = mergeChildren([child1, child2], [child2, child1], [child1, child2]);
99 | });
100 | it('should return same children', () => {
101 | expect(result.length).to.equal(2);
102 | });
103 | it('should order children correctly', () => {
104 | expect(result[0].key).to.equal(child2.key);
105 | expect(result[1].key).to.equal(child1.key);
106 | });
107 | it('should maintain "shown" classes to both children', () => {
108 | expect(result[0].classes).to.equal("shown");
109 | expect(result[1].classes).to.equal("shown");
110 | });
111 | });
112 |
113 | describe('when replacing', () => {
114 | beforeEach(() => {
115 | result = mergeChildren([child1], [child2], []);
116 | });
117 | it('should return all the children', () => {
118 | expect(result.length).to.equal(2);
119 | });
120 | it('should order children correctly', () => {
121 | expect(result[0].key).to.equal(child2.key);
122 | expect(result[1].key).to.equal(child1.key);
123 | });
124 | it('should add "add" classes to new child', () => {
125 | expect(result[0].classes).to.equal("add");
126 | });
127 | it('should add "hide" classes to old child', () => {
128 | expect(result[1].classes).to.equal("hide");
129 | });
130 | });
131 |
132 | describe('when changes are split by a persisting child', () => {
133 | beforeEach(() => {
134 | result = mergeChildren([child1, child2, child3], [child4, child2, child5], [child2]);
135 | });
136 | it('should return all the children', () => {
137 | expect(result.length).to.equal(5);
138 | });
139 | it('should order children correctly', () => {
140 | expect(result[0].key).to.equal(child4.key);
141 | expect(result[1].key).to.equal(child1.key);
142 | expect(result[2].key).to.equal(child2.key);
143 | expect(result[3].key).to.equal(child5.key);
144 | expect(result[4].key).to.equal(child3.key);
145 | });
146 | });
147 |
148 | });
149 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import filter from 'lodash/collection/filter';
5 | import remove from 'lodash/array/remove';
6 | import findIndex from 'lodash/array/findIndex';
7 |
8 | import cloneWithClasses from './clone-with-classes';
9 | import isIn from './is-in';
10 | import mergeChildren from './merge-children';
11 |
12 | const TransitionManager = React.createClass({
13 | displayName: 'TransitionManager',
14 | getInitialState() {
15 | const children = this.getChildren(this.props.children);
16 | return {
17 | adding: [],
18 | entering: [],
19 | removing: [],
20 | leaving: [],
21 | children: children.map(child => cloneWithClasses(child, 'shown'))
22 | };
23 | },
24 | getDefaultProps() {
25 | return {
26 | component: 'span'
27 | };
28 | },
29 | getChildren(children) {
30 | return children ? [].concat(children) : [];
31 | },
32 | componentWillReceiveProps(newProps) {
33 | const state = this.state;
34 | const targetChildren = this.getChildren(newProps.children);
35 | const currentRemoving = state.removing;
36 | const currentLeaving = state.leaving;
37 | const currentAdding = state.adding;
38 | const currentEntering = state.entering;
39 | const currentChildren = state.children;
40 | const targetRemoving = filter(currentChildren, child => !isIn(child, targetChildren) && !isIn(child, currentLeaving));
41 | const targetLeaving = filter(currentChildren, child => !isIn(child, targetChildren) && !isIn(child, targetRemoving));
42 | const targetAdding = filter(targetChildren, child => (isIn(child, currentAdding) && !isIn(child, targetRemoving) && !isIn(child, targetLeaving)) || !isIn(child, currentChildren));
43 | const targetEntering = filter(targetChildren, child => (isIn(child, currentEntering) && !isIn(child, targetRemoving) && !isIn(child, targetLeaving)) || isIn(child, currentLeaving));
44 | const persisting = filter(currentChildren, child => !isIn(child, targetAdding) && !isIn(child, targetEntering) && !isIn(child, targetRemoving) && !isIn(child, targetLeaving));
45 | const children = mergeChildren(currentChildren, targetChildren, persisting, targetEntering, targetLeaving);
46 | this.setState({
47 | adding: targetAdding,
48 | entering: targetEntering,
49 | removing: targetRemoving,
50 | leaving: targetLeaving,
51 | children: children.map(child => isIn(child, targetEntering) ? cloneWithClasses(child, 'add') : child)
52 | });
53 | },
54 | render() {
55 | return React.createElement(
56 | this.props.component,
57 | this.props,
58 | this.state.children.map(child => React.cloneElement(child, {
59 | ref: child.key
60 | }))
61 | );
62 | },
63 | timers: {
64 | adding: {},
65 | entering: {},
66 | removing: {},
67 | leaving: {}
68 | },
69 | componentDidUpdate() {
70 | this.state.adding.forEach((child) => {
71 | const key = child.key;
72 |
73 | // if doesn't exist, start an add timeout
74 | if(!this.timers.adding[key]) {
75 | this.timers.adding[key] = setTimeout(this.childAdded(key), 100);
76 | }
77 | });
78 |
79 | this.state.entering.forEach((child) => {
80 | const key = child.key;
81 |
82 | // remove any existing leave timeouts
83 | if(this.timers.leaving[key]) {
84 | clearTimeout(this.timers.leaving[key]);
85 | delete this.timers.leaving[key];
86 | }
87 |
88 | // if doesn't exist, start an enter timeout
89 | if(!this.timers.entering[key]) {
90 | this.timers.entering[key] = setTimeout(this.childEntered(key), this.props.duration);
91 | }
92 | });
93 |
94 | this.state.removing.forEach((child) => {
95 | const key = child.key;
96 |
97 | // if doesn't exist, start an add timeout
98 | if(!this.timers.removing[key]) {
99 | this.timers.removing[key] = setTimeout(this.childRemoved(key), 100);
100 | }
101 | });
102 |
103 | this.state.leaving.forEach((child) => {
104 | const key = child.key;
105 |
106 | // remove any existing enter timeouts
107 | if(this.timers.entering[key]) {
108 | clearTimeout(this.timers.entering[key]);
109 | delete this.timers.entering[key];
110 | }
111 |
112 | // if doesn't exist, start a leave timeout
113 | if(!this.timers.leaving[key]) {
114 | this.refs[key].componentWillLeave && this.refs[key].componentWillLeave();
115 | this.timers.leaving[key] = setTimeout(this.childLeft(key), this.props.duration);
116 | }
117 | });
118 | },
119 | childAdded(key) {
120 | return () => {
121 | const state = this.state;
122 | if(isIn({ key: key }, state.adding)) {
123 | const component = remove(state.adding, {
124 | key: key
125 | })[0];
126 | const newComponent = cloneWithClasses(component, 'show');
127 |
128 | clearTimeout(this.timers.adding[key]);
129 | delete this.timers.adding[key];
130 | state.entering.push(newComponent);
131 | state.children.splice(findIndex(state.children, 'key', key), 1, newComponent);
132 | this.setState(state);
133 | }
134 | };
135 | },
136 | childEntered(key) {
137 | return () => {
138 | const state = this.state;
139 | if(isIn({ key: key }, state.entering)) {
140 | const component = remove(state.entering, {
141 | key: key
142 | })[0];
143 | const newComponent = cloneWithClasses(component, 'shown');
144 |
145 | clearTimeout(this.timers.entering[key]);
146 | delete this.timers.entering[key];
147 | state.children.splice(findIndex(state.children, 'key', key), 1, newComponent);
148 | this.setState(state);
149 | }
150 | };
151 | },
152 | childRemoved(key) {
153 | return () => {
154 | const state = this.state;
155 | if(isIn({ key: key }, state.removing)) {
156 | const component = remove(state.removing, {
157 | key: key
158 | })[0];
159 | const newComponent = cloneWithClasses(component, 'hide');
160 |
161 | clearTimeout(this.timers.removing[key]);
162 | delete this.timers.removing[key];
163 | state.leaving.push(newComponent);
164 | state.children.splice(findIndex(state.children, 'key', key), 1, newComponent);
165 | this.setState(state);
166 | }
167 | };
168 | },
169 | childLeft(key) {
170 | return () => {
171 | const state = this.state;
172 | if(isIn({ key: key }, state.leaving)) {
173 | remove(state.leaving, {
174 | key: key
175 | });
176 | remove(state.children, {
177 | key: key
178 | });
179 |
180 | clearTimeout(this.timers.leaving[key]);
181 | delete this.timers.leaving[key];
182 | this.setState(state);
183 | }
184 | };
185 | }
186 | });
187 |
188 | export default TransitionManager;
189 |
--------------------------------------------------------------------------------