Skip to content

Commit eb90c21

Browse files
committed
Remove the react-transition-group dependency.
1 parent aed0d18 commit eb90c21

File tree

6 files changed

+222
-42
lines changed

6 files changed

+222
-42
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### Next
2+
3+
* [ENHANCEMENT] Remove the `react-transition-group` dependency. (#56)
4+
15
### v3.0.2 (7 October 2017)
26

37
* [BUGFIX] Fix updating children when the current child re-renders itself. (#53)

package-lock.json

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"url": "https://github.com/marnusw/react-css-transition-replace.git"
99
},
1010
"scripts": {
11+
"start": "gulp demo",
1112
"test": "echo \"No test implemented\" && exit 0",
1213
"prebuild": "./node_modules/.bin/rimraf lib && gulp lint",
1314
"build": "./node_modules/.bin/babel ./src --out-dir ./lib",
@@ -17,7 +18,6 @@
1718
"chain-function": "^1.0.0",
1819
"dom-helpers": "^3.2.0",
1920
"prop-types": "^15.6.0",
20-
"react-transition-group": "^1.2.1",
2121
"warning": "^3.0.0"
2222
},
2323
"peerDependencies": {

src/ReactCSSTransitionReplace.jsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* Adapted from ReactCSSTransitionGroup.js by Facebook
3-
*
4-
* @providesModule ReactCSSTransitionReplace
5-
*/
6-
71
import React from 'react'
82
import { findDOMNode } from 'react-dom'
93
import PropTypes from 'prop-types'
@@ -13,8 +7,7 @@ import warning from 'warning'
137
import raf from 'dom-helpers/util/requestAnimationFrame'
148

159
import ReactCSSTransitionReplaceChild from './ReactCSSTransitionReplaceChild'
16-
import { transitionTimeout } from 'react-transition-group/utils/PropTypes'
17-
import { nameShape } from './utils/PropTypes'
10+
import { nameShape, transitionTimeout } from './utils/PropTypes'
1811

1912
const reactCSSTransitionReplaceChild = React.createFactory(ReactCSSTransitionReplaceChild)
2013

Lines changed: 189 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,203 @@
11
/**
2-
* Uses react-transition-group/CSSTransitionGroupChild with the exception that
3-
* the first animation frame is skipped when starting new transitions since
2+
* From react-transition-group v1.2.1
3+
*
4+
* In addition, the first animation frame is skipped when starting new transitions since
45
* entering absolutely positioned elements in Chrome does not animate otherwise.
56
*/
6-
import ReactCSSTransitionReplaceChild from 'react-transition-group/CSSTransitionGroupChild'
7+
import addClass from 'dom-helpers/class/addClass'
8+
import removeClass from 'dom-helpers/class/removeClass'
79
import raf from 'dom-helpers/util/requestAnimationFrame'
10+
import { transitionEnd, animationEnd } from 'dom-helpers/transition/properties'
11+
import React from 'react'
12+
import PropTypes from 'prop-types'
13+
import { findDOMNode } from 'react-dom'
814

15+
import { nameShape } from './utils/PropTypes'
916

10-
ReactCSSTransitionReplaceChild.prototype.queueClassAndNode = function queueClassAndNode(className, node) {
11-
const _this2 = this
17+
let events = []
18+
if (transitionEnd) {
19+
events.push(transitionEnd)
20+
}
21+
if (animationEnd) {
22+
events.push(animationEnd)
23+
}
1224

13-
this.classNameAndNodeQueue.push({
14-
className: className,
15-
node: node,
16-
})
25+
function addEndListener(node, listener) {
26+
if (events.length) {
27+
events.forEach(e =>
28+
node.addEventListener(e, listener, false))
29+
} else {
30+
setTimeout(listener, 0)
31+
}
1732

18-
if (!this.rafHandle) {
19-
this.rafHandle = raf(function() {
20-
return _this2.flushClassNameAndNodeQueueOnNextFrame()
21-
})
33+
return () => {
34+
if (!events.length) {
35+
return
36+
}
37+
events.forEach(e => node.removeEventListener(e, listener, false))
2238
}
2339
}
2440

25-
// In Chrome the absolutely positioned children would not animate on enter
26-
// if the immediate animation frame is used so this skips to the next one.
27-
ReactCSSTransitionReplaceChild.prototype.flushClassNameAndNodeQueueOnNextFrame = function flushClassNameAndNodeQueueOnNextFrame() {
28-
const _this2 = this
41+
const propTypes = {
42+
children: PropTypes.node,
43+
name: nameShape.isRequired,
2944

30-
this.rafHandle = raf(function() {
31-
return _this2.flushClassNameAndNodeQueue()
32-
})
45+
// Once we require timeouts to be specified, we can remove the
46+
// boolean flags (appear etc.) and just accept a number
47+
// or a bool for the timeout flags (appearTimeout etc.)
48+
appear: PropTypes.bool,
49+
enter: PropTypes.bool,
50+
leave: PropTypes.bool,
51+
appearTimeout: PropTypes.number,
52+
enterTimeout: PropTypes.number,
53+
leaveTimeout: PropTypes.number,
3354
}
3455

35-
export default ReactCSSTransitionReplaceChild
56+
class CSSTransitionGroupChild extends React.Component {
57+
58+
static displayName = 'CSSTransitionGroupChild'
59+
60+
componentWillMount() {
61+
this.classNameAndNodeQueue = []
62+
this.transitionTimeouts = []
63+
}
64+
65+
componentWillUnmount() {
66+
this.unmounted = true
67+
68+
if (this.timeout) {
69+
clearTimeout(this.timeout)
70+
}
71+
this.transitionTimeouts.forEach((timeout) => {
72+
clearTimeout(timeout)
73+
})
74+
75+
this.classNameAndNodeQueue.length = 0
76+
}
77+
78+
transition(animationType, finishCallback, timeout) {
79+
let node = findDOMNode(this)
80+
81+
if (!node) {
82+
if (finishCallback) {
83+
finishCallback()
84+
}
85+
return
86+
}
87+
88+
let className = this.props.name[animationType] || this.props.name + '-' + animationType
89+
let activeClassName = this.props.name[animationType + 'Active'] || className + '-active'
90+
let timer = null
91+
let removeListeners
92+
93+
addClass(node, className)
94+
95+
// Need to do this to actually trigger a transition.
96+
this.queueClassAndNode(activeClassName, node)
97+
98+
// Clean-up the animation after the specified delay
99+
let finish = (e) => {
100+
if (e && e.target !== node) {
101+
return
102+
}
103+
104+
clearTimeout(timer)
105+
if (removeListeners) {
106+
removeListeners()
107+
}
108+
109+
removeClass(node, className)
110+
removeClass(node, activeClassName)
111+
112+
if (removeListeners) {
113+
removeListeners()
114+
}
115+
116+
// Usually this optional callback is used for informing an owner of
117+
// a leave animation and telling it to remove the child.
118+
if (finishCallback) {
119+
finishCallback()
120+
}
121+
}
122+
123+
if (timeout) {
124+
timer = setTimeout(finish, timeout)
125+
this.transitionTimeouts.push(timer)
126+
} else if (transitionEnd) {
127+
removeListeners = addEndListener(node, finish)
128+
}
129+
}
130+
131+
queueClassAndNode(className, node) {
132+
this.classNameAndNodeQueue.push({
133+
className,
134+
node,
135+
})
136+
137+
if (!this.rafHandle) {
138+
// The first animation frame is skipped when starting new transitions since
139+
// entering absolutely positioned elements in Chrome does not animate otherwise.
140+
this.rafHandle = raf(() => this.flushClassNameAndNodeQueueOnNextFrame())
141+
}
142+
}
143+
144+
flushClassNameAndNodeQueueOnNextFrame() {
145+
this.rafHandle = raf(() => this.flushClassNameAndNodeQueue())
146+
}
147+
148+
flushClassNameAndNodeQueue() {
149+
if (!this.unmounted) {
150+
this.classNameAndNodeQueue.forEach((obj) => {
151+
// This is for to force a repaint,
152+
// which is necessary in order to transition styles when adding a class name.
153+
/* eslint-disable no-unused-expressions */
154+
obj.node.scrollTop
155+
/* eslint-enable no-unused-expressions */
156+
addClass(obj.node, obj.className)
157+
})
158+
}
159+
this.classNameAndNodeQueue.length = 0
160+
this.rafHandle = null
161+
}
162+
163+
componentWillAppear = (done) => {
164+
if (this.props.appear) {
165+
this.transition('appear', done, this.props.appearTimeout)
166+
} else {
167+
done()
168+
}
169+
}
170+
171+
componentWillEnter = (done) => {
172+
if (this.props.enter) {
173+
this.transition('enter', done, this.props.enterTimeout)
174+
} else {
175+
done()
176+
}
177+
}
178+
179+
componentWillLeave = (done) => {
180+
if (this.props.leave) {
181+
this.transition('leave', done, this.props.leaveTimeout)
182+
} else {
183+
done()
184+
}
185+
}
186+
187+
render() {
188+
const props = {...this.props}
189+
delete props.name
190+
delete props.appear
191+
delete props.enter
192+
delete props.leave
193+
delete props.appearTimeout
194+
delete props.enterTimeout
195+
delete props.leaveTimeout
196+
delete props.children
197+
return React.cloneElement(React.Children.only(this.props.children), props)
198+
}
199+
}
200+
201+
CSSTransitionGroupChild.propTypes = propTypes
202+
203+
export default CSSTransitionGroupChild

src/utils/PropTypes.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
import PropTypes from 'prop-types'
22

3+
export function transitionTimeout(transitionType) {
4+
let timeoutPropName = 'transition' + transitionType + 'Timeout'
5+
let enabledPropName = 'transition' + transitionType
6+
7+
return (props) => {
8+
// If the transition is enabled
9+
if (props[enabledPropName]) {
10+
// If no timeout duration is provided
11+
if (props[timeoutPropName] == null) {
12+
return new Error(
13+
timeoutPropName + ' wasn\'t supplied to CSSTransitionGroup: ' +
14+
'this can cause unreliable animations and won\'t be supported in ' +
15+
'a future version of React. See ' +
16+
'https://fb.me/react-animation-transition-group-timeout for more ' +
17+
'information.',
18+
)
19+
20+
// If the duration isn't a number
21+
} else if (typeof props[timeoutPropName] !== 'number') {
22+
return new Error(timeoutPropName + ' must be a number (in milliseconds)')
23+
}
24+
}
25+
26+
return null
27+
}
28+
}
29+
330
export const nameShape = PropTypes.oneOfType([
431
PropTypes.string,
532
PropTypes.shape({

0 commit comments

Comments
 (0)