Added support for synthetic animation/transition events.
Miles Johnson committed
Feb 8, 2016 at 21:31 UTC
260353e977848c202e267ba2b9a328075650f52b
8 files changed
+287
-43
docs/docs/ref-05-events.md
+37
@@ -139,6 +139,7 @@ DOMEventTarget relatedTarget
139
140
These focus events work on all elements in the React DOM, not just form elements.
141
142
+
143
### Form Events
144
145
Event names:
@@ -246,6 +247,7 @@ number deltaY
247
number deltaZ
248
```
249
250
+
251
### Media Events
252
253
Event names:
@@ -254,6 +256,7 @@ Event names:
256
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting
257
```
258
259
+
260
### Image Events
261
262
Event names:
@@ -261,3 +264,37 @@ Event names:
264
```
265
onLoad onError
266
```
267
+
268
+
269
+### Animation Events
270
+
271
+Event names:
272
+
273
+```
274
+onAnimationStart onAnimationEnd onAnimationIteration
275
+```
276
+
277
+Properties:
278
+
279
+```javascript
280
+string animationName
281
+string pseudoElement
282
+float elapsedTime
283
+```
284
+
285
+
286
+### Transition Events
287
+
288
+Event names:
289
+
290
+```
291
+onTransitionEnd
292
+```
293
+
294
+Properties:
295
+
296
+```javascript
297
+string propertyName
298
+string pseudoElement
299
+float elapsedTime
300
+```
src/addons/transitions/ReactTransitionEvents.js
+7
-43
@@ -13,56 +13,20 @@
13
14
var ExecutionEnvironment = require('ExecutionEnvironment');
15
16
-/**
17
- * EVENT_NAME_MAP is used to determine which event fired when a
18
- * transition/animation ends, based on the style property used to
19
- * define that event.
20
- */
21
-var EVENT_NAME_MAP = {
22
- transitionend: {
23
- 'transition': 'transitionend',
24
- 'WebkitTransition': 'webkitTransitionEnd',
25
- 'MozTransition': 'mozTransitionEnd',
26
- 'OTransition': 'oTransitionEnd',
27
- 'msTransition': 'MSTransitionEnd',
28
- },
29
-
30
- animationend: {
31
- 'animation': 'animationend',
32
- 'WebkitAnimation': 'webkitAnimationEnd',
33
- 'MozAnimation': 'mozAnimationEnd',
34
- 'OAnimation': 'oAnimationEnd',
35
- 'msAnimation': 'MSAnimationEnd',
36
- },
37
-};
16
+var getVendorPrefixedEventName = require('getVendorPrefixedEventName');
17
18
var endEvents = [];
19
20
function detectEvents() {
42
- var testEl = document.createElement('div');
43
- var style = testEl.style;
44
-
45
- // On some platforms, in particular some releases of Android 4.x,
46
- // the un-prefixed "animation" and "transition" properties are defined on the
47
- // style object but the events that fire will still be prefixed, so we need
48
- // to check if the un-prefixed events are useable, and if not remove them
49
- // from the map
50
- if (!('AnimationEvent' in window)) {
51
- delete EVENT_NAME_MAP.animationend.animation;
52
- }
21
+ var animEnd = getVendorPrefixedEventName('animationend');
22
+ var transEnd = getVendorPrefixedEventName('transitionend');
23
54
- if (!('TransitionEvent' in window)) {
55
- delete EVENT_NAME_MAP.transitionend.transition;
24
+ if (animEnd) {
25
+ endEvents.push(animEnd);
26
}
27
58
- for (var baseEventName in EVENT_NAME_MAP) {
59
- var baseEvents = EVENT_NAME_MAP[baseEventName];
60
- for (var styleName in baseEvents) {
61
- if (styleName in style) {
62
- endEvents.push(baseEvents[styleName]);
63
- break;
64
- }
65
- }
28
+ if (transEnd) {
29
+ endEvents.push(transEnd);
30
}
31
}
32
src/renderers/dom/client/ReactBrowserEventEmitter.js
+5
@@ -17,6 +17,7 @@ var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
17
var ViewportMetrics = require('ViewportMetrics');
18
19
var assign = require('Object.assign');
20
+var getVendorPrefixedEventName = require('getVendorPrefixedEventName');
21
var isEventSupported = require('isEventSupported');
22
23
/**
@@ -83,6 +84,9 @@ var reactTopListenersCounter = 0;
84
// events so we don't include them here
85
var topEventMapping = {
86
topAbort: 'abort',
87
+ topAnimationEnd: getVendorPrefixedEventName('animationend') || 'animationend',
88
+ topAnimationIteration: getVendorPrefixedEventName('animationiteration') || 'animationiteration',
89
+ topAnimationStart: getVendorPrefixedEventName('animationstart') || 'animationstart',
90
topBlur: 'blur',
91
topCanPlay: 'canplay',
92
topCanPlayThrough: 'canplaythrough',
@@ -139,6 +143,7 @@ var topEventMapping = {
143
topTouchEnd: 'touchend',
144
topTouchMove: 'touchmove',
145
topTouchStart: 'touchstart',
146
+ topTransitionEnd: getVendorPrefixedEventName('transitionend') || 'transitionend',
147
topVolumeChange: 'volumechange',
148
topWaiting: 'waiting',
149
topWheel: 'wheel',
src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js
+38
@@ -15,6 +15,7 @@ var EventConstants = require('EventConstants');
15
var EventListener = require('EventListener');
16
var EventPropagators = require('EventPropagators');
17
var ReactDOMComponentTree = require('ReactDOMComponentTree');
18
+var SyntheticAnimationEvent = require('SyntheticAnimationEvent');
19
var SyntheticClipboardEvent = require('SyntheticClipboardEvent');
20
var SyntheticEvent = require('SyntheticEvent');
21
var SyntheticFocusEvent = require('SyntheticFocusEvent');
@@ -22,6 +23,7 @@ var SyntheticKeyboardEvent = require('SyntheticKeyboardEvent');
23
var SyntheticMouseEvent = require('SyntheticMouseEvent');
24
var SyntheticDragEvent = require('SyntheticDragEvent');
25
var SyntheticTouchEvent = require('SyntheticTouchEvent');
26
+var SyntheticTransitionEvent = require('SyntheticTransitionEvent');
27
var SyntheticUIEvent = require('SyntheticUIEvent');
28
var SyntheticWheelEvent = require('SyntheticWheelEvent');
29
@@ -39,6 +41,24 @@ var eventTypes = {
41
captured: keyOf({onAbortCapture: true}),
42
},
43
},
44
+ animationEnd: {
45
+ phasedRegistrationNames: {
46
+ bubbled: keyOf({onAnimationEnd: true}),
47
+ captured: keyOf({onAnimationEndCapture: true}),
48
+ },
49
+ },
50
+ animationIteration: {
51
+ phasedRegistrationNames: {
52
+ bubbled: keyOf({onAnimationIteration: true}),
53
+ captured: keyOf({onAnimationIterationCapture: true}),
54
+ },
55
+ },
56
+ animationStart: {
57
+ phasedRegistrationNames: {
58
+ bubbled: keyOf({onAnimationStart: true}),
59
+ captured: keyOf({onAnimationStartCapture: true}),
60
+ },
61
+ },
62
blur: {
63
phasedRegistrationNames: {
64
bubbled: keyOf({onBlur: true}),
@@ -365,6 +385,12 @@ var eventTypes = {
385
captured: keyOf({onTouchStartCapture: true}),
386
},
387
},
388
+ transitionEnd: {
389
+ phasedRegistrationNames: {
390
+ bubbled: keyOf({onTransitionEnd: true}),
391
+ captured: keyOf({onTransitionEndCapture: true}),
392
+ },
393
+ },
394
volumeChange: {
395
phasedRegistrationNames: {
396
bubbled: keyOf({onVolumeChange: true}),
@@ -387,6 +413,9 @@ var eventTypes = {
413
414
var topLevelEventsToDispatchConfig = {
415
topAbort: eventTypes.abort,
416
+ topAnimationEnd: eventTypes.animationEnd,
417
+ topAnimationIteration: eventTypes.animationIteration,
418
+ topAnimationStart: eventTypes.animationStart,
419
topBlur: eventTypes.blur,
420
topCanPlay: eventTypes.canPlay,
421
topCanPlayThrough: eventTypes.canPlayThrough,
@@ -441,6 +470,7 @@ var topLevelEventsToDispatchConfig = {
470
topTouchEnd: eventTypes.touchEnd,
471
topTouchMove: eventTypes.touchMove,
472
topTouchStart: eventTypes.touchStart,
473
+ topTransitionEnd: eventTypes.transitionEnd,
474
topVolumeChange: eventTypes.volumeChange,
475
topWaiting: eventTypes.waiting,
476
topWheel: eventTypes.wheel,
@@ -549,6 +579,14 @@ var SimpleEventPlugin = {
579
case topLevelTypes.topTouchStart:
580
EventConstructor = SyntheticTouchEvent;
581
break;
582
+ case topLevelTypes.topAnimationEnd:
583
+ case topLevelTypes.topAnimationIteration:
584
+ case topLevelTypes.topAnimationStart:
585
+ EventConstructor = SyntheticAnimationEvent;
586
+ break;
587
+ case topLevelTypes.topTransitionEnd:
588
+ EventConstructor = SyntheticTransitionEvent;
589
+ break;
590
case topLevelTypes.topScroll:
591
EventConstructor = SyntheticUIEvent;
592
break;
src/renderers/dom/client/syntheticEvents/SyntheticAnimationEvent.js
new
+47
@@ -0,0 +1,47 @@
1
+/**
2
+ * Copyright 2013-present, 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 SyntheticAnimationEvent
10
+ */
11
+
12
+'use strict';
13
+
14
+var SyntheticEvent = require('SyntheticEvent');
15
+
16
+/**
17
+ * @interface Event
18
+ * @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
19
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
20
+ */
21
+var AnimationEventInterface = {
22
+ animationName: null,
23
+ elapsedTime: null,
24
+ pseudoElement: null,
25
+};
26
+
27
+/**
28
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
29
+ * @param {string} dispatchMarker Marker identifying the event target.
30
+ * @param {object} nativeEvent Native browser event.
31
+ * @extends {SyntheticEvent}
32
+ */
33
+function SyntheticAnimationEvent(
34
+ dispatchConfig,
35
+ dispatchMarker,
36
+ nativeEvent,
37
+ nativeEventTarget
38
+) {
39
+ return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
40
+}
41
+
42
+SyntheticEvent.augmentClass(
43
+ SyntheticAnimationEvent,
44
+ AnimationEventInterface
45
+);
46
+
47
+module.exports = SyntheticAnimationEvent;
src/renderers/dom/client/syntheticEvents/SyntheticTransitionEvent.js
new
+47
@@ -0,0 +1,47 @@
1
+/**
2
+ * Copyright 2013-present, 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 SyntheticTransitionEvent
10
+ */
11
+
12
+'use strict';
13
+
14
+var SyntheticEvent = require('SyntheticEvent');
15
+
16
+/**
17
+ * @interface Event
18
+ * @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
19
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
20
+ */
21
+var TransitionEventInterface = {
22
+ propertyName: null,
23
+ elapsedTime: null,
24
+ pseudoElement: null,
25
+};
26
+
27
+/**
28
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
29
+ * @param {string} dispatchMarker Marker identifying the event target.
30
+ * @param {object} nativeEvent Native browser event.
31
+ * @extends {SyntheticEvent}
32
+ */
33
+function SyntheticTransitionEvent(
34
+ dispatchConfig,
35
+ dispatchMarker,
36
+ nativeEvent,
37
+ nativeEventTarget
38
+) {
39
+ return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
40
+}
41
+
42
+SyntheticEvent.augmentClass(
43
+ SyntheticTransitionEvent,
44
+ TransitionEventInterface
45
+);
46
+
47
+module.exports = SyntheticTransitionEvent;
src/renderers/dom/client/utils/getVendorPrefixedEventName.js
new
+102
@@ -0,0 +1,102 @@
1
+/**
2
+ * Copyright 2013-present, 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 getVendorPrefixedEventName
10
+ */
11
+
12
+'use strict';
13
+
14
+var ExecutionEnvironment = require('ExecutionEnvironment');
15
+
16
+/**
17
+ * Generate a mapping of standard vendor prefixes using the defined style property and event name.
18
+ *
19
+ * @param {string} styleProp
20
+ * @param {string} eventName
21
+ * @returns {object}
22
+ */
23
+function makePrefixMap(styleProp, eventName) {
24
+ var prefixes = {};
25
+
26
+ prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
27
+ prefixes['Webkit' + styleProp] = 'webkit' + eventName;
28
+ prefixes['Moz' + styleProp] = 'moz' + eventName;
29
+ prefixes['ms' + styleProp] = 'MS' + eventName;
30
+ prefixes['O' + styleProp] = 'o' + eventName.toLowerCase();
31
+
32
+ return prefixes;
33
+}
34
+
35
+/**
36
+ * A list of event names to a configurable list of vendor prefixes.
37
+ */
38
+var vendorPrefixes = {
39
+ animationend: makePrefixMap('Animation', 'AnimationEnd'),
40
+ animationiteration: makePrefixMap('Animation', 'AnimationIteration'),
41
+ animationstart: makePrefixMap('Animation', 'AnimationStart'),
42
+ transitionend: makePrefixMap('Transition', 'TransitionEnd'),
43
+};
44
+
45
+/**
46
+ * Event names that have already been detected and prefixed (if applicable).
47
+ */
48
+var prefixedEventNames = {};
49
+
50
+/**
51
+ * Element to check for prefixes on.
52
+ */
53
+var style = {};
54
+
55
+/**
56
+ * Bootstrap if a DOM exists.
57
+ */
58
+if (ExecutionEnvironment.canUseDOM) {
59
+ style = document.createElement('div').style;
60
+
61
+ // On some platforms, in particular some releases of Android 4.x,
62
+ // the un-prefixed "animation" and "transition" properties are defined on the
63
+ // style object but the events that fire will still be prefixed, so we need
64
+ // to check if the un-prefixed events are usable, and if not remove them from the map.
65
+ if (!('AnimationEvent' in window)) {
66
+ delete vendorPrefixes.animationend.animation;
67
+ delete vendorPrefixes.animationiteration.animation;
68
+ delete vendorPrefixes.animationstart.animation;
69
+ }
70
+
71
+ // Same as above
72
+ if (!('TransitionEvent' in window)) {
73
+ delete vendorPrefixes.transitionend.transition;
74
+ }
75
+}
76
+
77
+/**
78
+ * Attempts to determine the correct vendor prefixed event name.
79
+ *
80
+ * @param {string} eventName
81
+ * @returns {string}
82
+ */
83
+function getVendorPrefixedEventName(eventName) {
84
+ if (prefixedEventNames[eventName]) {
85
+ return prefixedEventNames[eventName];
86
+
87
+ } else if (!vendorPrefixes[eventName]) {
88
+ return eventName;
89
+ }
90
+
91
+ var prefixMap = vendorPrefixes[eventName];
92
+
93
+ for (var styleProp in prefixMap) {
94
+ if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) {
95
+ return prefixedEventNames[eventName] = prefixMap[styleProp];
96
+ }
97
+ }
98
+
99
+ return '';
100
+}
101
+
102
+module.exports = getVendorPrefixedEventName;
src/renderers/shared/event/EventConstants.js
+4
@@ -20,6 +20,9 @@ var PropagationPhases = keyMirror({bubbled: null, captured: null});
20
*/
21
var topLevelTypes = keyMirror({
22
topAbort: null,
23
+ topAnimationEnd: null,
24
+ topAnimationIteration: null,
25
+ topAnimationStart: null,
26
topBlur: null,
27
topCanPlay: null,
28
topCanPlayThrough: null,
@@ -80,6 +83,7 @@ var topLevelTypes = keyMirror({
83
topTouchEnd: null,
84
topTouchMove: null,
85
topTouchStart: null,
86
+ topTransitionEnd: null,
87
topVolumeChange: null,
88
topWaiting: null,
89
topWheel: null,