Added support for synthetic animation/transition events.

This commit is contained in:
Miles Johnson
2016-02-25 23:52:32 -08:00
parent 61966ff88b
commit 4bec526fd0
8 changed files with 287 additions and 43 deletions
+37
View File
@@ -139,6 +139,7 @@ DOMEventTarget relatedTarget
These focus events work on all elements in the React DOM, not just form elements.
### Form Events
Event names:
@@ -246,6 +247,7 @@ number deltaY
number deltaZ
```
### Media Events
Event names:
@@ -254,6 +256,7 @@ Event names:
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting
```
### Image Events
Event names:
@@ -261,3 +264,37 @@ Event names:
```
onLoad onError
```
### Animation Events
Event names:
```
onAnimationStart onAnimationEnd onAnimationIteration
```
Properties:
```javascript
string animationName
string pseudoElement
float elapsedTime
```
### Transition Events
Event names:
```
onTransitionEnd
```
Properties:
```javascript
string propertyName
string pseudoElement
float elapsedTime
```
@@ -13,56 +13,20 @@
var ExecutionEnvironment = require('ExecutionEnvironment');
/**
* EVENT_NAME_MAP is used to determine which event fired when a
* transition/animation ends, based on the style property used to
* define that event.
*/
var EVENT_NAME_MAP = {
transitionend: {
'transition': 'transitionend',
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'mozTransitionEnd',
'OTransition': 'oTransitionEnd',
'msTransition': 'MSTransitionEnd',
},
animationend: {
'animation': 'animationend',
'WebkitAnimation': 'webkitAnimationEnd',
'MozAnimation': 'mozAnimationEnd',
'OAnimation': 'oAnimationEnd',
'msAnimation': 'MSAnimationEnd',
},
};
var getVendorPrefixedEventName = require('getVendorPrefixedEventName');
var endEvents = [];
function detectEvents() {
var testEl = document.createElement('div');
var style = testEl.style;
var animEnd = getVendorPrefixedEventName('animationend');
var transEnd = getVendorPrefixedEventName('transitionend');
// On some platforms, in particular some releases of Android 4.x,
// the un-prefixed "animation" and "transition" properties are defined on the
// style object but the events that fire will still be prefixed, so we need
// to check if the un-prefixed events are useable, and if not remove them
// from the map
if (!('AnimationEvent' in window)) {
delete EVENT_NAME_MAP.animationend.animation;
if (animEnd) {
endEvents.push(animEnd);
}
if (!('TransitionEvent' in window)) {
delete EVENT_NAME_MAP.transitionend.transition;
}
for (var baseEventName in EVENT_NAME_MAP) {
var baseEvents = EVENT_NAME_MAP[baseEventName];
for (var styleName in baseEvents) {
if (styleName in style) {
endEvents.push(baseEvents[styleName]);
break;
}
}
if (transEnd) {
endEvents.push(transEnd);
}
}
@@ -17,6 +17,7 @@ var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ViewportMetrics = require('ViewportMetrics');
var assign = require('Object.assign');
var getVendorPrefixedEventName = require('getVendorPrefixedEventName');
var isEventSupported = require('isEventSupported');
/**
@@ -83,6 +84,9 @@ var reactTopListenersCounter = 0;
// events so we don't include them here
var topEventMapping = {
topAbort: 'abort',
topAnimationEnd: getVendorPrefixedEventName('animationend') || 'animationend',
topAnimationIteration: getVendorPrefixedEventName('animationiteration') || 'animationiteration',
topAnimationStart: getVendorPrefixedEventName('animationstart') || 'animationstart',
topBlur: 'blur',
topCanPlay: 'canplay',
topCanPlayThrough: 'canplaythrough',
@@ -139,6 +143,7 @@ var topEventMapping = {
topTouchEnd: 'touchend',
topTouchMove: 'touchmove',
topTouchStart: 'touchstart',
topTransitionEnd: getVendorPrefixedEventName('transitionend') || 'transitionend',
topVolumeChange: 'volumechange',
topWaiting: 'waiting',
topWheel: 'wheel',
@@ -15,6 +15,7 @@ var EventConstants = require('EventConstants');
var EventListener = require('EventListener');
var EventPropagators = require('EventPropagators');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var SyntheticAnimationEvent = require('SyntheticAnimationEvent');
var SyntheticClipboardEvent = require('SyntheticClipboardEvent');
var SyntheticEvent = require('SyntheticEvent');
var SyntheticFocusEvent = require('SyntheticFocusEvent');
@@ -22,6 +23,7 @@ var SyntheticKeyboardEvent = require('SyntheticKeyboardEvent');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
var SyntheticDragEvent = require('SyntheticDragEvent');
var SyntheticTouchEvent = require('SyntheticTouchEvent');
var SyntheticTransitionEvent = require('SyntheticTransitionEvent');
var SyntheticUIEvent = require('SyntheticUIEvent');
var SyntheticWheelEvent = require('SyntheticWheelEvent');
@@ -39,6 +41,24 @@ var eventTypes = {
captured: keyOf({onAbortCapture: true}),
},
},
animationEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onAnimationEnd: true}),
captured: keyOf({onAnimationEndCapture: true}),
},
},
animationIteration: {
phasedRegistrationNames: {
bubbled: keyOf({onAnimationIteration: true}),
captured: keyOf({onAnimationIterationCapture: true}),
},
},
animationStart: {
phasedRegistrationNames: {
bubbled: keyOf({onAnimationStart: true}),
captured: keyOf({onAnimationStartCapture: true}),
},
},
blur: {
phasedRegistrationNames: {
bubbled: keyOf({onBlur: true}),
@@ -365,6 +385,12 @@ var eventTypes = {
captured: keyOf({onTouchStartCapture: true}),
},
},
transitionEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onTransitionEnd: true}),
captured: keyOf({onTransitionEndCapture: true}),
},
},
volumeChange: {
phasedRegistrationNames: {
bubbled: keyOf({onVolumeChange: true}),
@@ -387,6 +413,9 @@ var eventTypes = {
var topLevelEventsToDispatchConfig = {
topAbort: eventTypes.abort,
topAnimationEnd: eventTypes.animationEnd,
topAnimationIteration: eventTypes.animationIteration,
topAnimationStart: eventTypes.animationStart,
topBlur: eventTypes.blur,
topCanPlay: eventTypes.canPlay,
topCanPlayThrough: eventTypes.canPlayThrough,
@@ -441,6 +470,7 @@ var topLevelEventsToDispatchConfig = {
topTouchEnd: eventTypes.touchEnd,
topTouchMove: eventTypes.touchMove,
topTouchStart: eventTypes.touchStart,
topTransitionEnd: eventTypes.transitionEnd,
topVolumeChange: eventTypes.volumeChange,
topWaiting: eventTypes.waiting,
topWheel: eventTypes.wheel,
@@ -549,6 +579,14 @@ var SimpleEventPlugin = {
case topLevelTypes.topTouchStart:
EventConstructor = SyntheticTouchEvent;
break;
case topLevelTypes.topAnimationEnd:
case topLevelTypes.topAnimationIteration:
case topLevelTypes.topAnimationStart:
EventConstructor = SyntheticAnimationEvent;
break;
case topLevelTypes.topTransitionEnd:
EventConstructor = SyntheticTransitionEvent;
break;
case topLevelTypes.topScroll:
EventConstructor = SyntheticUIEvent;
break;
@@ -0,0 +1,47 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule SyntheticAnimationEvent
*/
'use strict';
var SyntheticEvent = require('SyntheticEvent');
/**
* @interface Event
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
*/
var AnimationEventInterface = {
animationName: null,
elapsedTime: null,
pseudoElement: null,
};
/**
* @param {object} dispatchConfig Configuration used to dispatch this event.
* @param {string} dispatchMarker Marker identifying the event target.
* @param {object} nativeEvent Native browser event.
* @extends {SyntheticEvent}
*/
function SyntheticAnimationEvent(
dispatchConfig,
dispatchMarker,
nativeEvent,
nativeEventTarget
) {
return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
}
SyntheticEvent.augmentClass(
SyntheticAnimationEvent,
AnimationEventInterface
);
module.exports = SyntheticAnimationEvent;
@@ -0,0 +1,47 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule SyntheticTransitionEvent
*/
'use strict';
var SyntheticEvent = require('SyntheticEvent');
/**
* @interface Event
* @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
* @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
*/
var TransitionEventInterface = {
propertyName: null,
elapsedTime: null,
pseudoElement: null,
};
/**
* @param {object} dispatchConfig Configuration used to dispatch this event.
* @param {string} dispatchMarker Marker identifying the event target.
* @param {object} nativeEvent Native browser event.
* @extends {SyntheticEvent}
*/
function SyntheticTransitionEvent(
dispatchConfig,
dispatchMarker,
nativeEvent,
nativeEventTarget
) {
return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
}
SyntheticEvent.augmentClass(
SyntheticTransitionEvent,
TransitionEventInterface
);
module.exports = SyntheticTransitionEvent;
@@ -0,0 +1,102 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule getVendorPrefixedEventName
*/
'use strict';
var ExecutionEnvironment = require('ExecutionEnvironment');
/**
* Generate a mapping of standard vendor prefixes using the defined style property and event name.
*
* @param {string} styleProp
* @param {string} eventName
* @returns {object}
*/
function makePrefixMap(styleProp, eventName) {
var prefixes = {};
prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
prefixes['Webkit' + styleProp] = 'webkit' + eventName;
prefixes['Moz' + styleProp] = 'moz' + eventName;
prefixes['ms' + styleProp] = 'MS' + eventName;
prefixes['O' + styleProp] = 'o' + eventName.toLowerCase();
return prefixes;
}
/**
* A list of event names to a configurable list of vendor prefixes.
*/
var vendorPrefixes = {
animationend: makePrefixMap('Animation', 'AnimationEnd'),
animationiteration: makePrefixMap('Animation', 'AnimationIteration'),
animationstart: makePrefixMap('Animation', 'AnimationStart'),
transitionend: makePrefixMap('Transition', 'TransitionEnd'),
};
/**
* Event names that have already been detected and prefixed (if applicable).
*/
var prefixedEventNames = {};
/**
* Element to check for prefixes on.
*/
var style = {};
/**
* Bootstrap if a DOM exists.
*/
if (ExecutionEnvironment.canUseDOM) {
style = document.createElement('div').style;
// On some platforms, in particular some releases of Android 4.x,
// the un-prefixed "animation" and "transition" properties are defined on the
// style object but the events that fire will still be prefixed, so we need
// to check if the un-prefixed events are usable, and if not remove them from the map.
if (!('AnimationEvent' in window)) {
delete vendorPrefixes.animationend.animation;
delete vendorPrefixes.animationiteration.animation;
delete vendorPrefixes.animationstart.animation;
}
// Same as above
if (!('TransitionEvent' in window)) {
delete vendorPrefixes.transitionend.transition;
}
}
/**
* Attempts to determine the correct vendor prefixed event name.
*
* @param {string} eventName
* @returns {string}
*/
function getVendorPrefixedEventName(eventName) {
if (prefixedEventNames[eventName]) {
return prefixedEventNames[eventName];
} else if (!vendorPrefixes[eventName]) {
return eventName;
}
var prefixMap = vendorPrefixes[eventName];
for (var styleProp in prefixMap) {
if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) {
return prefixedEventNames[eventName] = prefixMap[styleProp];
}
}
return '';
}
module.exports = getVendorPrefixedEventName;
@@ -20,6 +20,9 @@ var PropagationPhases = keyMirror({bubbled: null, captured: null});
*/
var topLevelTypes = keyMirror({
topAbort: null,
topAnimationEnd: null,
topAnimationIteration: null,
topAnimationStart: null,
topBlur: null,
topCanPlay: null,
topCanPlayThrough: null,
@@ -80,6 +83,7 @@ var topLevelTypes = keyMirror({
topTouchEnd: null,
topTouchMove: null,
topTouchStart: null,
topTransitionEnd: null,
topVolumeChange: null,
topWaiting: null,
topWheel: null,