├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── HISTORY.md
├── README.md
├── examples
├── alert.html
├── alert.js
├── hide-todo.html
├── hide-todo.js
├── todo.html
└── todo.js
├── index.js
├── package.json
├── src
├── CSSCore.jsx
├── CSSTransitionGroup.jsx
├── CSSTransitionGroupChild.jsx
├── ReactTransitionChildMapping.jsx
├── ReactTransitionEvents.jsx
└── index.js
└── tests
├── CSSTransitionGroup.spec.js
├── index.spec.css
├── index.spec.js
└── runner.html
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # Unix-style newlines with a newline ending every file
5 | [*.{js,css}]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | .idea/
4 | .ipr
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn/
16 | *.swp
17 | *.swo
18 | *.pyc
19 | *.pyo
20 | .build
21 | node_modules
22 | .cache
23 | dist
24 | assets/**/*.css
25 | build
26 | lib
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.cfg
3 | nohup.out
4 | *.iml
5 | .idea/
6 | .ipr
7 | .iws
8 | *~
9 | ~*
10 | *.diff
11 | *.log
12 | *.patch
13 | *.bak
14 | .DS_Store
15 | Thumbs.db
16 | .project
17 | .*proj
18 | .svn/
19 | *.swp
20 | out/
21 | .build
22 | node_modules
23 | .cache
24 | examples
25 | tests
26 | src
27 | /index.js
28 | .*
29 | assets/**/*.less
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | notifications:
4 | email:
5 | - yiminghe@gmail.com
6 |
7 | node_js:
8 | - 0.12
9 |
10 | before_install:
11 | - |
12 | if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/'
13 | then
14 | echo "Only docs were updated, stopping build process."
15 | exit
16 | fi
17 | npm install mocha-phantomjs -g
18 | phantomjs --version
19 |
20 | script:
21 | - |
22 | if [ "$TEST_TYPE" = test ]; then
23 | npm test
24 | else
25 | npm run $TEST_TYPE
26 | fi
27 |
28 | env:
29 | matrix:
30 | - TEST_TYPE=lint
31 | - TEST_TYPE=browser-test
32 | - TEST_TYPE=browser-test-cover
33 | - TEST_TYPE=saucelabs
34 | global:
35 | - secure: IUflgQ8L65l4Xfn/ra5j/kH/nyxmZuz7Pf/qAqZl32da6OMzt3iCofpcM0RNpZtJcJoGkcE3yZpc5wDEEKUa8l+mUQxC7oeKUrJO0bit4KqO/2Jaa2QMyt0xd3agPs0vdnsz0ZHkNuB3iUjwY8sEDOdliVLw+3gTbI1mmPBEA4w=
36 | - secure: e/j2zwyPTIXZFU2DW3WEj53Twem8XV90chOnoqfNAk+7+xMW/d5xQY2CKueuBEMKL9QdIcaZDOWf1WHd702tkoTQd/EGiJhsN8UcRvF3i9wXxOxuayU5xyB/34F7jjo1v+THgZGLNqykz60bUqj+Z5vmB4xPs6uWGzlLgic1cQQ=
37 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # History
2 | ----
3 |
4 | ## 2.1.0 / 2015-05-19
5 |
6 | `new` add exclusive prop
7 |
8 | ## 2.0.0 / 2015-05-19
9 |
10 | `new` [#2](https://github.com/react-component/css-transition-group/issues/2) add showProp prop
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is deprecated in favor of https://github.com/react-component/animate
2 |
--------------------------------------------------------------------------------
/examples/alert.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/css-transition-group/c654070ebf14a2625255e4e96b86b1690f5da996/examples/alert.html
--------------------------------------------------------------------------------
/examples/alert.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var style = '.alert-outer{\
4 | position: fixed;\
5 | width:100%;\
6 | top: 50px;\
7 | z-index: 9999;\
8 | }\
9 | \
10 | .alert-outer .alert {\
11 | background:yellow;\
12 | width: 600px;\
13 | padding:20px;\
14 | margin-left:auto;\
15 | margin-right:auto;\
16 | }\
17 | \
18 | .alert-outer p{\
19 | padding: 15px;\
20 | }\
21 | \
22 | .alert-anim-enter {\
23 | opacity: 0.01;\
24 | transition: opacity 1s ease-in;\
25 | -webkit-transition: opacity 1s ease-in;\
26 | }\
27 | \
28 | .alert-anim-enter.alert-anim-enter-active {\
29 | opacity: 1;\
30 | }\
31 | \
32 | .alert-anim-leave {\
33 | opacity: 1;\
34 | transition: opacity 1s ease-in;\
35 | -webkit-transition: opacity 1s ease-in;\
36 | }\
37 | \
38 | .alert-anim-leave.alert-anim-leave-active {\
39 | opacity: 0.01;\
40 | }';
41 |
42 | var React = require('react');
43 | var CSSTransitionGroup = require('rc-css-transition-group');
44 | var seed = 0;
45 |
46 | var Alert = React.createClass({
47 | protoTypes: {
48 | time: React.PropTypes.number,
49 | type: React.PropTypes.number,
50 | str: React.PropTypes.string,
51 | onEnd: React.PropTypes.func
52 | },
53 |
54 | getDefaultProps: function () {
55 | return {
56 | onEnd: function () {
57 | },
58 | time: 2000,
59 | type: 'success'
60 | }
61 | },
62 |
63 | componentDidMount: function () {
64 | var props = this.props;
65 | setTimeout(function () {
66 | props.onEnd();
67 | }, props.time);
68 | },
69 |
70 | render: function () {
71 | var props = this.props;
72 | return
{props.str}
;
73 | }
74 | });
75 |
76 |
77 | var AlertGroup = React.createClass({
78 | getInitialState: function () {
79 | return {
80 | alerts: []
81 | }
82 | },
83 | addAlert: function (a) {
84 | this.setState({
85 | alerts: this.state.alerts.concat(a)
86 | });
87 | },
88 | onEnd: function (key) {
89 | var alerts = this.state.alerts;
90 | var ret = [];
91 | var target;
92 | alerts.forEach(function (a) {
93 | if (a.key === key) {
94 | target = a;
95 | } else {
96 | ret.push(a);
97 | }
98 | });
99 | if (target) {
100 | this.setState({
101 | alerts: ret
102 | }, function () {
103 | if (target.callback) {
104 | target.callback();
105 | }
106 | })
107 | }
108 | },
109 | render: function () {
110 | var alerts = this.state.alerts;
111 | var self = this;
112 | var children = alerts.map(function (a) {
113 | if (!a.key) {
114 | seed++;
115 | a.key = seed + '';
116 | }
117 | return
118 | });
119 | return
120 | {children}
121 |
;
122 | }
123 | });
124 |
125 | var alertGroup;
126 |
127 | function alert(str, time, type, callback) {
128 | if (!alertGroup) {
129 | var div = document.createElement('div');
130 | document.body.appendChild(div);
131 | alertGroup = React.render(, div);
132 | }
133 | alertGroup.addAlert({
134 | str: str,
135 | time: time,
136 | type: type,
137 | callback: callback
138 | });
139 | }
140 |
141 | function onClick() {
142 | for (var i = 0; i < 4; i++) {
143 | (function (i) {
144 | setTimeout(function () {
145 | alert(i);
146 | }, 1000 * i);
147 | })(i);
148 | }
149 | }
150 |
151 | React.render(
152 |
notification
153 |
154 |
155 | ,
156 | document.getElementById('__react-content'));
157 |
--------------------------------------------------------------------------------
/examples/hide-todo.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/css-transition-group/c654070ebf14a2625255e4e96b86b1690f5da996/examples/hide-todo.html
--------------------------------------------------------------------------------
/examples/hide-todo.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var style = '.example-enter {\
4 | opacity: 0.01;\
5 | transition: opacity 1s ease-in;\
6 | }\
7 | \
8 | .example-enter.example-enter-active {\
9 | opacity: 1;\
10 | }\
11 | \
12 | .example-leave {\
13 | opacity: 1;\
14 | transition: opacity 1s ease-in;\
15 | }\
16 | \
17 | .example-leave.example-leave-active {\
18 | opacity: 0.01;\
19 | }\
20 | \
21 | .item {\
22 | width:100px;\
23 | border:1px solid red;\
24 | padding:10px;\
25 | margin:10px;\
26 | }';
27 |
28 | var CSSTransitionGroup = require('rc-css-transition-group');
29 | var React = require('react');
30 | var assign = require('object-assign');
31 |
32 | var Todo = React.createClass({
33 | getDefaultProps: function () {
34 | return {
35 | visible: true,
36 | end: function () {
37 | }
38 | }
39 | },
40 | componentWillUnmount: function () {
41 | console.log('componentWillUnmount');
42 | console.log(this.props.children);
43 | this.props.end();
44 | },
45 | render: function () {
46 | var props = this.props;
47 | return
50 | {props.children}
51 |
;
52 | }
53 | });
54 | var TodoList = React.createClass({
55 | getInitialState: function () {
56 | return {
57 | items: [
58 | {content: 'hello', visible: true},
59 | {content: 'world', visible: true},
60 | {content: 'click', visible: true},
61 | {content: 'me', visible: true}]
62 | };
63 | },
64 | handleHide: function (i, item) {
65 | var newItems = this.state.items.concat([]);
66 | newItems.forEach((n, index)=> {
67 | newItems[index] = assign({}, n, {
68 | visible: true
69 | });
70 | });
71 | newItems[i] = assign({}, item, {
72 | visible: false
73 | });
74 | this.setState({items: newItems});
75 | },
76 | render: function () {
77 | var items = this.state.items.map(function (item, i) {
78 | return (
79 |
82 | {item.content}
83 |
84 | );
85 | }.bind(this));
86 | return (
87 |
88 |
91 | {items}
92 |
93 |
94 | );
95 | }
96 | });
97 |
98 | React.render(
99 |
Hide Todo
100 |
101 |
102 | , document.getElementById('__react-content'));
103 |
--------------------------------------------------------------------------------
/examples/todo.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/css-transition-group/c654070ebf14a2625255e4e96b86b1690f5da996/examples/todo.html
--------------------------------------------------------------------------------
/examples/todo.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var style = '.example-enter {\
4 | opacity: 0.01;\
5 | transition: opacity 1s ease-in;\
6 | }\
7 | \
8 | .example-enter.example-enter-active {\
9 | opacity: 1;\
10 | }\
11 | \
12 | .example-leave {\
13 | opacity: 1;\
14 | transition: opacity 1s ease-in;\
15 | }\
16 | \
17 | .example-leave.example-leave-active {\
18 | opacity: 0.01;\
19 | }\
20 | \
21 | .item {\
22 | width:100px;\
23 | border:1px solid red;\
24 | padding:10px;\
25 | margin:10px;\
26 | }';
27 |
28 | var CSSTransitionGroup = require('rc-css-transition-group');
29 | var React = require('react');
30 | var Todo = React.createClass({
31 | getDefaultProps: function () {
32 | return {
33 | end: function () {
34 | }
35 | }
36 | },
37 | componentWillUnmount: function () {
38 | console.log('componentWillUnmount');
39 | console.log(this.props.children);
40 | this.props.end();
41 | },
42 | render: function () {
43 | var props = this.props;
44 | return
45 | {props.children}
46 |
;
47 | }
48 | });
49 | var TodoList = React.createClass({
50 | getInitialState: function () {
51 | return {items: ['hello', 'world', 'click', 'me']};
52 | },
53 | handleAdd: function () {
54 | var newItems =
55 | this.state.items.concat([prompt('Enter some text')]);
56 | this.setState({items: newItems});
57 | },
58 | handleRemove: function (i) {
59 | var newItems = this.state.items;
60 | newItems.splice(i, 1);
61 | this.setState({items: newItems});
62 | },
63 | render: function () {
64 | var items = this.state.items.map(function (item, i) {
65 | return (
66 |
67 | {item}
68 |
69 | );
70 | }.bind(this));
71 | return (
72 |
73 |
74 |
75 | {items}
76 |
77 |
78 | );
79 | }
80 | });
81 |
82 | React.render(
83 |
Todo
84 |
85 |
86 | , document.getElementById('__react-content'));
87 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./src/');
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-css-transition-group",
3 | "version": "2.1.4",
4 | "description": "css-transition-group ui component for react",
5 | "keywords": [
6 | "react",
7 | "react-component",
8 | "react-css-transition-group",
9 | "css-transition-group"
10 | ],
11 | "main": "./lib/index",
12 | "homepage": "http://github.com/react-component/css-transition-group",
13 | "author": "",
14 | "repository": {
15 | "type": "git",
16 | "url": "git@github.com:react-component/css-transition-group.git"
17 | },
18 | "bugs": {
19 | "url": "http://github.com/react-component/css-transition-group/issues"
20 | },
21 | "licenses": "MIT",
22 | "config": {
23 | "port": 8010
24 | },
25 | "scripts": {
26 | "build": "rc-tools run build",
27 | "precommit": "rc-tools run precommit",
28 | "less": "rc-tools run less",
29 | "gh-pages": "rc-tools run gh-pages",
30 | "history": "rc-tools run history",
31 | "start": "node --harmony node_modules/.bin/rc-server",
32 | "publish": "rc-tools run tag",
33 | "lint": "rc-tools run lint",
34 | "saucelabs": "node --harmony node_modules/.bin/rc-tools run saucelabs",
35 | "browser-test": "node --harmony node_modules/.bin/rc-tools run browser-test",
36 | "browser-test-cover": "node --harmony node_modules/.bin/rc-tools run browser-test-cover"
37 | },
38 | "devDependencies": {
39 | "expect.js": "~0.3.1",
40 | "object-assign": "~2.0.0",
41 | "precommit-hook": "^1.0.7",
42 | "rc-server": "3.x",
43 | "rc-tools": "3.x",
44 | "react": "~0.13.0"
45 | },
46 | "precommit": [
47 | "precommit"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/src/CSSCore.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var SPACE = ' ';
4 | var RE_CLASS = /[\n\t\r]/g;
5 |
6 | var norm = function (elemClass) {
7 | return (SPACE + elemClass + SPACE).replace(RE_CLASS, SPACE);
8 | };
9 |
10 | module.exports = {
11 | addClass(elem, className) {
12 | elem.className += ' ' + className;
13 | },
14 |
15 | removeClass(elem, needle) {
16 | var elemClass = elem.className.trim();
17 | var className = norm(elemClass);
18 | needle = needle.trim();
19 | needle = SPACE + needle + SPACE;
20 | // 一个 cls 有可能多次出现:'link link2 link link3 link'
21 | while (className.indexOf(needle) >= 0) {
22 | className = className.replace(needle, SPACE);
23 | }
24 | elem.className = className.trim();
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/src/CSSTransitionGroup.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react');
4 | var ReactTransitionChildMapping = require('./ReactTransitionChildMapping');
5 | var CSSTransitionGroupChild = require('./CSSTransitionGroupChild');
6 |
7 | var CSSTransitionGroup = React.createClass({
8 | protoTypes: {
9 | component: React.PropTypes.any,
10 | transitionName: React.PropTypes.string.isRequired,
11 | transitionEnter: React.PropTypes.bool,
12 | transitionLeave: React.PropTypes.bool
13 | },
14 |
15 | getDefaultProps() {
16 | return {
17 | component: 'span',
18 | transitionEnter: true,
19 | transitionLeave: true
20 | };
21 | },
22 |
23 | getInitialState() {
24 | var ret = [];
25 | React.Children.forEach(this.props.children, (c)=> {
26 | ret.push(c);
27 | });
28 | return {
29 | children: ret
30 | };
31 | },
32 |
33 | componentWillMount() {
34 | this.currentlyTransitioningKeys = {};
35 | this.keysToEnter = [];
36 | this.keysToLeave = [];
37 | },
38 |
39 | componentWillReceiveProps(nextProps) {
40 | var nextChildMapping = [];
41 | var showProp = this.props.showProp;
42 | var exclusive = this.props.exclusive;
43 |
44 | React.Children.forEach(nextProps.children, (c)=> {
45 | nextChildMapping.push(c);
46 | });
47 |
48 | // // last props children if exclusive
49 | var prevChildMapping = exclusive ? this.props.children : this.state.children;
50 |
51 | var newChildren = ReactTransitionChildMapping.mergeChildMappings(
52 | prevChildMapping,
53 | nextChildMapping
54 | );
55 |
56 | if (showProp) {
57 | newChildren = newChildren.map((c)=> {
58 | if (!c.props[showProp] && ReactTransitionChildMapping.isShownInChildren(prevChildMapping, c, showProp)) {
59 | var newProps = {};
60 | newProps[showProp] = true;
61 | c = React.cloneElement(c, newProps);
62 | }
63 | return c;
64 | });
65 | }
66 |
67 | if (exclusive) {
68 | // make middle state children invalid
69 | // restore to last props children
70 | newChildren.forEach((c)=> {
71 | this.stop(c.key);
72 | });
73 | }
74 |
75 | this.setState({
76 | children: newChildren
77 | });
78 |
79 | nextChildMapping.forEach((c)=> {
80 | var key = c.key;
81 | var hasPrev = prevChildMapping && ReactTransitionChildMapping.inChildren(prevChildMapping, c);
82 | if (showProp) {
83 | if (hasPrev) {
84 | var showInPrev = ReactTransitionChildMapping.isShownInChildren(prevChildMapping, c, showProp);
85 | var showInNow = c.props[showProp];
86 | if (!showInPrev && showInNow && !this.currentlyTransitioningKeys[key]) {
87 | this.keysToEnter.push(key);
88 | }
89 | }
90 | } else if (!hasPrev && !this.currentlyTransitioningKeys[key]) {
91 | this.keysToEnter.push(key);
92 | }
93 | });
94 |
95 | prevChildMapping.forEach((c)=> {
96 | var key = c.key;
97 | var hasNext = nextChildMapping && ReactTransitionChildMapping.inChildren(nextChildMapping, c);
98 | if (showProp) {
99 | if (hasNext) {
100 | var showInNext = ReactTransitionChildMapping.isShownInChildren(nextChildMapping, c, showProp);
101 | var showInNow = c.props[showProp];
102 | if (!showInNext && showInNow && !this.currentlyTransitioningKeys[key]) {
103 | this.keysToLeave.push(key);
104 | }
105 | }
106 | } else if (!hasNext && !this.currentlyTransitioningKeys[key]) {
107 | this.keysToLeave.push(key);
108 | }
109 | });
110 | },
111 |
112 | performEnter(key) {
113 | this.currentlyTransitioningKeys[key] = true;
114 | var component = this.refs[key];
115 | if (component.componentWillEnter) {
116 | component.componentWillEnter(
117 | this._handleDoneEntering.bind(this, key)
118 | );
119 | } else {
120 | this._handleDoneEntering(key);
121 | }
122 | },
123 |
124 | _handleDoneEntering(key) {
125 | //console.log('_handleDoneEntering, ', key);
126 | delete this.currentlyTransitioningKeys[key];
127 | var currentChildMapping = this.props.children;
128 | var showProp = this.props.showProp;
129 | if (!currentChildMapping || (
130 | !showProp && !ReactTransitionChildMapping.inChildrenByKey(currentChildMapping, key)
131 | ) || (
132 | showProp && !ReactTransitionChildMapping.isShownInChildrenByKey(currentChildMapping, key, showProp)
133 | )) {
134 | // This was removed before it had fully entered. Remove it.
135 | //console.log('releave ',key);
136 | this.performLeave(key);
137 | } else {
138 | this.setState({children: currentChildMapping});
139 | }
140 | },
141 |
142 | stop(key) {
143 | delete this.currentlyTransitioningKeys[key];
144 | var component = this.refs[key];
145 | if (component) {
146 | component.stop();
147 | }
148 | },
149 |
150 | performLeave(key) {
151 | this.currentlyTransitioningKeys[key] = true;
152 |
153 | var component = this.refs[key];
154 | if (component.componentWillLeave) {
155 | component.componentWillLeave(this._handleDoneLeaving.bind(this, key));
156 | } else {
157 | // Note that this is somewhat dangerous b/c it calls setState()
158 | // again, effectively mutating the component before all the work
159 | // is done.
160 | this._handleDoneLeaving(key);
161 | }
162 | },
163 |
164 | _handleDoneLeaving(key) {
165 | //console.log('_handleDoneLeaving, ', key);
166 | delete this.currentlyTransitioningKeys[key];
167 | var showProp = this.props.showProp;
168 | var currentChildMapping = this.props.children;
169 | if (showProp && currentChildMapping &&
170 | ReactTransitionChildMapping.isShownInChildrenByKey(currentChildMapping, key, showProp)) {
171 | this.performEnter(key);
172 | } else if (!showProp && currentChildMapping && ReactTransitionChildMapping.inChildrenByKey(currentChildMapping, key)) {
173 | // This entered again before it fully left. Add it again.
174 | //console.log('reenter ',key);
175 | this.performEnter(key);
176 | } else {
177 | this.setState({children: currentChildMapping});
178 | }
179 | },
180 |
181 | componentDidUpdate() {
182 | var keysToEnter = this.keysToEnter;
183 | this.keysToEnter = [];
184 | keysToEnter.forEach(this.performEnter);
185 | var keysToLeave = this.keysToLeave;
186 | this.keysToLeave = [];
187 | keysToLeave.forEach(this.performLeave);
188 | },
189 |
190 | render() {
191 | var props = this.props;
192 | var children = this.state.children.map((child) => {
193 | return {child};
199 | });
200 | var Component = this.props.component;
201 | return {children};
202 | }
203 | });
204 | module.exports = CSSTransitionGroup;
205 |
--------------------------------------------------------------------------------
/src/CSSTransitionGroupChild.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2014, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @typechecks
10 | * @providesModule ReactCSSTransitionGroupChild
11 | */
12 |
13 | 'use strict';
14 |
15 | var React = require('react');
16 |
17 | var CSSCore = require('./CSSCore');
18 | var ReactTransitionEvents = require('./ReactTransitionEvents');
19 |
20 | var TICK = 17;
21 |
22 | var ReactCSSTransitionGroupChild = React.createClass({
23 | transition(animationType, finishCallback) {
24 | var node = this.getDOMNode();
25 | var className = this.props.name + '-' + animationType;
26 | var activeClassName = className + '-active';
27 |
28 | if (this.endListener) {
29 | this.endListener();
30 | }
31 |
32 | this.endListener = (e) => {
33 | if (e && e.target !== node) {
34 | return;
35 | }
36 |
37 | CSSCore.removeClass(node, className);
38 | CSSCore.removeClass(node, activeClassName);
39 |
40 | ReactTransitionEvents.removeEndEventListener(node, this.endListener);
41 | this.endListener = null;
42 |
43 | // Usually this optional callback is used for informing an owner of
44 | // a leave animation and telling it to remove the child.
45 | if (finishCallback) {
46 | finishCallback();
47 | }
48 | };
49 |
50 | ReactTransitionEvents.addEndEventListener(node, this.endListener);
51 |
52 | CSSCore.addClass(node, className);
53 |
54 | // Need to do this to actually trigger a transition.
55 | this.queueClass(activeClassName);
56 | },
57 |
58 | queueClass(className) {
59 | this.classNameQueue.push(className);
60 |
61 | if (!this.timeout) {
62 | this.timeout = setTimeout(this.flushClassNameQueue, TICK);
63 | }
64 | },
65 |
66 | stop() {
67 | //console.log('force stop')
68 | if (this.timeout) {
69 | clearTimeout(this.timeout);
70 | this.classNameQueue.length = 0;
71 | this.timeout = null;
72 | }
73 | if (this.endListener) {
74 | this.endListener();
75 | }
76 | },
77 |
78 | flushClassNameQueue() {
79 | if (this.isMounted()) {
80 | this.classNameQueue.forEach(
81 | CSSCore.addClass.bind(CSSCore, this.getDOMNode())
82 | );
83 | }
84 | this.classNameQueue.length = 0;
85 | this.timeout = null;
86 | },
87 |
88 | componentWillMount() {
89 | this.classNameQueue = [];
90 | },
91 |
92 | componentWillUnmount() {
93 | if (this.timeout) {
94 | clearTimeout(this.timeout);
95 | }
96 | },
97 |
98 | componentWillEnter(done) {
99 | if (this.props.enter) {
100 | this.transition('enter', done);
101 | } else {
102 | done();
103 | }
104 | },
105 |
106 | componentWillLeave(done) {
107 | if (this.props.leave) {
108 | this.transition('leave', done);
109 | } else {
110 | done();
111 | }
112 | },
113 |
114 | render() {
115 | return this.props.children;
116 | }
117 | });
118 |
119 | module.exports = ReactCSSTransitionGroupChild;
120 |
--------------------------------------------------------------------------------
/src/ReactTransitionChildMapping.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function inChildren(children, child) {
4 | var found = 0;
5 | children.forEach(function (c) {
6 | if (found) {
7 | return;
8 | }
9 | found = c.key === child.key;
10 | });
11 | return found;
12 | }
13 |
14 | module.exports = {
15 | inChildren: inChildren,
16 |
17 | isShownInChildren(children, child, showProp) {
18 | var found = 0;
19 | children.forEach(function (c) {
20 | if (found) {
21 | return;
22 | }
23 | found = (c.key === child.key && c.props[showProp]);
24 | });
25 | return found;
26 | },
27 |
28 | inChildrenByKey(children, key) {
29 | var found = 0;
30 | children.forEach(function (c) {
31 | if (found) {
32 | return;
33 | }
34 | found = c.key === key;
35 | });
36 | return found;
37 | },
38 |
39 | isShownInChildrenByKey(children, key, showProp) {
40 | var found = 0;
41 | children.forEach(function (c) {
42 | if (found) {
43 | return;
44 | }
45 | found = c.key === key && c.props[showProp];
46 | });
47 | return found;
48 | },
49 |
50 | mergeChildMappings(prev, next) {
51 | var ret = [];
52 |
53 | // For each key of `next`, the list of keys to insert before that key in
54 | // the combined list
55 | var nextChildrenPending = {};
56 | var pendingChildren = [];
57 | prev.forEach(function (c) {
58 | if (inChildren(next, c)) {
59 | if (pendingChildren.length) {
60 | nextChildrenPending[c.key] = pendingChildren;
61 | pendingChildren = [];
62 | }
63 | } else {
64 | pendingChildren.push(c);
65 | }
66 | });
67 |
68 | next.forEach(function (c) {
69 | if (nextChildrenPending.hasOwnProperty(c.key)) {
70 | ret = ret.concat(nextChildrenPending[c.key]);
71 | }
72 | ret.push(c);
73 | });
74 |
75 | ret = ret.concat(pendingChildren);
76 |
77 | return ret;
78 | }
79 | };
80 |
--------------------------------------------------------------------------------
/src/ReactTransitionEvents.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-2014, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule ReactTransitionEvents
10 | */
11 |
12 | 'use strict';
13 | /**
14 | * EVENT_NAME_MAP is used to determine which event fired when a
15 | * transition/animation ends, based on the style property used to
16 | * define that event.
17 | */
18 | var EVENT_NAME_MAP = {
19 | transitionend: {
20 | transition: 'transitionend',
21 | WebkitTransition: 'webkitTransitionEnd',
22 | MozTransition: 'mozTransitionEnd',
23 | OTransition: 'oTransitionEnd',
24 | msTransition: 'MSTransitionEnd'
25 | },
26 |
27 | animationend: {
28 | animation: 'animationend',
29 | WebkitAnimation: 'webkitAnimationEnd',
30 | MozAnimation: 'mozAnimationEnd',
31 | OAnimation: 'oAnimationEnd',
32 | msAnimation: 'MSAnimationEnd'
33 | }
34 | };
35 |
36 | var endEvents = [];
37 |
38 | function detectEvents() {
39 | var testEl = document.createElement('div');
40 | var style = testEl.style;
41 |
42 | // On some platforms, in particular some releases of Android 4.x,
43 | // the un-prefixed "animation" and "transition" properties are defined on the
44 | // style object but the events that fire will still be prefixed, so we need
45 | // to check if the un-prefixed events are useable, and if not remove them
46 | // from the map
47 | if (!('AnimationEvent' in window)) {
48 | delete EVENT_NAME_MAP.animationend.animation;
49 | }
50 |
51 | if (!('TransitionEvent' in window)) {
52 | delete EVENT_NAME_MAP.transitionend.transition;
53 | }
54 |
55 | for (var baseEventName in EVENT_NAME_MAP) {
56 | var baseEvents = EVENT_NAME_MAP[baseEventName];
57 | for (var styleName in baseEvents) {
58 | if (styleName in style) {
59 | endEvents.push(baseEvents[styleName]);
60 | break;
61 | }
62 | }
63 | }
64 | }
65 |
66 | if (typeof window !== 'undefined') {
67 | detectEvents();
68 | }
69 |
70 | // We use the raw {add|remove}EventListener() call because EventListener
71 | // does not know how to remove event listeners and we really should
72 | // clean up. Also, these events are not triggered in older browsers
73 | // so we should be A-OK here.
74 |
75 | function addEventListener(node, eventName, eventListener) {
76 | node.addEventListener(eventName, eventListener, false);
77 | }
78 |
79 | function removeEventListener(node, eventName, eventListener) {
80 | node.removeEventListener(eventName, eventListener, false);
81 | }
82 |
83 | var ReactTransitionEvents = {
84 | addEndEventListener(node, eventListener) {
85 | if (endEvents.length === 0) {
86 | // If CSS transitions are not supported, trigger an "end animation"
87 | // event immediately.
88 | window.setTimeout(eventListener, 0);
89 | return;
90 | }
91 | endEvents.forEach(function (endEvent) {
92 | addEventListener(node, endEvent, eventListener);
93 | });
94 | },
95 |
96 | endEvents: endEvents,
97 |
98 | removeEndEventListener(node, eventListener) {
99 | if (endEvents.length === 0) {
100 | return;
101 | }
102 | endEvents.forEach(function (endEvent) {
103 | removeEventListener(node, endEvent, eventListener);
104 | });
105 | }
106 | };
107 |
108 | module.exports = ReactTransitionEvents;
109 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./CSSTransitionGroup');
4 |
--------------------------------------------------------------------------------
/tests/CSSTransitionGroup.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | // cao not run in phantomjs, fail!
5 | var CSSTransitionGroup = require('../');
6 | var React = require('react/addons');
7 | var TestUtils = React.addons.TestUtils;
8 | var Simulate = TestUtils.Simulate;
9 | var expect = require('expect.js');
10 | require('./index.spec.css');
11 |
12 | var Todo = React.createClass({
13 | getDefaultProps: function () {
14 | return {
15 | end: function () {
16 | }
17 | }
18 | },
19 |
20 | componentWillUnmount: function () {
21 | this.props.end();
22 | },
23 |
24 | render: function () {
25 | var props = this.props;
26 | return
27 | {props.children}
28 |
;
29 | }
30 | });
31 | var TodoList = React.createClass({
32 | getInitialState: function () {
33 | return {items: ['hello', 'world', 'click', 'me']};
34 | },
35 |
36 | handleAdd: function (item) {
37 | var newItems =
38 | this.state.items.concat(item);
39 | this.setState({items: newItems});
40 | },
41 |
42 | handleRemove: function (i) {
43 | var newItems = this.state.items;
44 | newItems.splice(i, 1);
45 | this.setState({items: newItems});
46 | },
47 |
48 | render: function () {
49 | var items = this.state.items.map(function (item, i) {
50 | return (
51 |
52 | {item}
53 |
54 | );
55 | }.bind(this));
56 | return (
57 |
58 |
59 | {items}
60 |
61 |
62 | );
63 | }
64 | });
65 |
66 | describe('CSSTransitionGroup', function () {
67 | var list;
68 | var container = document.createElement('div');
69 | document.body.appendChild(container);
70 |
71 | beforeEach(function (done) {
72 | React.render(, container, function () {
73 | list = this;
74 | done();
75 | });
76 | });
77 |
78 | afterEach(function () {
79 | React.unmountComponentAtNode(container);
80 | });
81 |
82 | it('create works', function () {
83 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item').length).to.be(4);
84 | });
85 |
86 | var ReactTransitionEvents = require('../src/ReactTransitionEvents');
87 | if (!ReactTransitionEvents.endEvents.length) {
88 | return;
89 | }
90 |
91 | it('transitionLeave works', function (done) {
92 | this.timeout(5999);
93 | list.handleRemove(0);
94 | setTimeout(function () {
95 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item').length).to.be(4);
96 | if (!window.callPhantom) {
97 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item')[0].getDOMNode().className)
98 | .to.contain('example-leave');
99 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item')[0].getDOMNode().className)
100 | .to.contain('example-leave-active');
101 | }
102 | }, 100);
103 | setTimeout(function () {
104 | if (!window.callPhantom) {
105 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item').length).to.be(3);
106 | }
107 | done();
108 | }, 1400);
109 | });
110 |
111 | it('transitionLeave works', function (done) {
112 | this.timeout(5999);
113 | list.handleAdd(Date.now());
114 | setTimeout(function () {
115 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item').length).to.be(5);
116 | if (!window.callPhantom) {
117 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item')[4].getDOMNode().className)
118 | .to.contain('example-enter');
119 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item')[4].getDOMNode().className)
120 | .to.contain('example-enter-active');
121 | }
122 | }, 100);
123 | setTimeout(function () {
124 | if (!window.callPhantom) {
125 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item').length).to.be(5);
126 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item')[4].getDOMNode().className)
127 | .not.to.contain('example-enter');
128 | expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item')[4].getDOMNode().className)
129 | .not.to.contain('example-enter-active');
130 | }
131 | done();
132 | }, 1400);
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/tests/index.spec.css:
--------------------------------------------------------------------------------
1 | .example-enter {
2 | opacity: 0.01;
3 | transition: opacity 1s ease-in;
4 | }
5 |
6 | .example-enter.example-enter-active {
7 | opacity: 1;
8 | }
9 |
10 | .example-leave {
11 | opacity: 1;
12 | transition: opacity 1s ease-in;
13 | }
14 |
15 | .example-leave.example-leave-active {
16 | opacity: 0.01;
17 | }
18 |
19 | .item {
20 | width:100px;
21 | border:1px solid red;
22 | padding:10px;
23 | margin:10px;
24 | }
25 |
--------------------------------------------------------------------------------
/tests/index.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./CSSTransitionGroup.spec');
4 |
--------------------------------------------------------------------------------
/tests/runner.html:
--------------------------------------------------------------------------------
1 | stub
--------------------------------------------------------------------------------