diff --git a/src/collide/animator.js b/src/collide/animator.js
new file mode 100644
index 0000000000..3669c0206c
--- /dev/null
+++ b/src/collide/animator.js
@@ -0,0 +1,370 @@
+import {raf} from 'ionic2/util/dom'
+
+
+
+class GlobalAnimator {
+
+ constructor() {
+ console.log('Animator instance', Math.random())
+
+ this.isTicking = false
+ this.calls = []
+ }
+
+ run(animation) {
+ animation.timeStart = null;
+ this.calls.push(animation);
+
+ if (!this.isTicking) {
+ this.tick()
+ }
+ }
+
+ /* Note: All calls to Animator are pushed to the this.calls array, which is fully iterated through upon each tick. */
+ tick (timestamp) {
+ console.log('tick', timestamp)
+
+ /* An empty timestamp argument indicates that this is the first tick occurence since ticking was turned on.
+ We leverage this metadata to fully ignore the first tick pass since RAF's initial pass is fired whenever
+ the browser's next tick sync time occurs, which results in the first elements subjected to Animator
+ calls being animated out of sync with any elements animated immediately thereafter. In short, we ignore
+ the first RAF tick pass so that elements being immediately consecutively animated -- instead of simultaneously animated
+ by the same Animator call -- are properly batched into the same initial RAF tick and consequently remain in sync thereafter. */
+ if (timestamp) {
+ /* We ignore RAF's high resolution timestamp since it can be significantly offset when the browser is
+ under high stress; we opt for choppiness over allowing the browser to drop huge chunks of frames. */
+ var timeCurrent = (new Date).getTime();
+
+ /********************
+ Call Iteration
+ ********************/
+
+ var callsLength = this.calls.length;
+
+ /* To speed up iterating over this array, it is compacted (falsey items -- calls that have completed -- are removed)
+ when its length has ballooned to a point that can impact tick performance. This only becomes necessary when animation
+ has been continuous with many elements over a long period of time; whenever all active calls are completed, completeCall() clears this.calls. */
+ if (callsLength > 10000) {
+ this.calls = compactSparseArray(this.calls);
+ }
+
+ /* Iterate through each active call. */
+ for (var i = 0; i < callsLength; i++) {
+ /* When a Animator call is completed, its this.calls entry is set to false. Continue on to the next call. */
+ if (!this.calls[i]) {
+ continue;
+ }
+
+ /************************
+ Call-Wide Variables
+ ************************/
+
+ var animation = this.calls[i];
+ var timeStart = animation.timeStart;
+ var firstTick = !!timeStart;
+ var opts = animation.options();
+
+ /* If timeStart is undefined, then this is the first time that this call has been processed by tick().
+ We assign timeStart now so that its value is as close to the real animation start time as possible.
+ (Conversely, had timeStart been defined when this call was added to this.calls, the delay
+ between that time and now would cause the first few frames of the tween to be skipped since
+ percentComplete is calculated relative to timeStart.) */
+ /* Further, subtract 16ms (the approximate resolution of RAF) from the current time value so that the
+ first tick iteration isn't wasted by animating at 0% tween completion, which would produce the
+ same style value as the element's current value. */
+ if (!timeStart) {
+ timeStart = timeCurrent - 16;
+ }
+
+ /* The tween's completion percentage is relative to the tween's start time, not the tween's start value
+ (which would result in unpredictable tween durations since JavaScript's timers are not particularly accurate).
+ Accordingly, we ensure that percentComplete does not exceed 1. */
+ var percentComplete = Math.min((timeCurrent - timeStart) / opts.duration, 1);
+
+
+ /**********************
+ Element Iteration
+ **********************/
+
+ for (var j = 0, elementCount = animation.elements.length; j < elementCount; j++) {
+ var element = animation.elements[j];
+
+ /* Check to see if this element has been deleted midway through the animation */
+ if (!element.parent) {
+ continue;
+ }
+
+ var transformPropertyExists = false;
+
+
+ /**********************************
+ Display & Visibility Toggling
+ **********************************/
+
+ /* If the display option is set to non-"none", set it upfront so that the element can become visible before tweening begins.
+ (Otherwise, display's "none" value is set in completeCall() once the animation has completed.) */
+ if (opts.display !== undefined && opts.display !== null && opts.display !== "none") {
+ if (opts.display === "flex") {
+
+ for (var x = 0; x < flexValues.length; x++) {
+ setPropertyValue(element, "display", flexValues[x]);
+ }
+ }
+
+ setPropertyValue(element, "display", opts.display);
+ }
+
+ /* Same goes with the visibility option, but its "none" equivalent is "hidden". */
+ if (opts.visibility !== undefined && opts.visibility !== "hidden") {
+ setPropertyValue(element, "visibility", opts.visibility);
+ }
+
+
+ /************************
+ Property Iteration
+ ************************/
+
+ /* For every element, iterate through each property. */
+ for (var property in tweensContainer) {
+ /* Note: In addition to property tween data, tweensContainer contains a reference to its associated element. */
+ if (property !== "element") {
+ var tween = tweensContainer[property],
+ currentValue,
+ /* Easing can either be a pre-genereated function or a string that references a pre-registered easing
+ on the Animator.Easings object. In either case, return the appropriate easing *function*. */
+ easing = Type.isString(tween.easing) ? Animator.Easings[tween.easing] : tween.easing;
+
+ /******************************
+ Current Value Calculation
+ ******************************/
+
+ /* If this is the last tick pass (if we've reached 100% completion for this tween),
+ ensure that currentValue is explicitly set to its target endValue so that it's not subjected to any rounding. */
+ if (percentComplete === 1) {
+ currentValue = tween.endValue;
+ /* Otherwise, calculate currentValue based on the current delta from startValue. */
+ } else {
+ var tweenDelta = tween.endValue - tween.startValue;
+ currentValue = tween.startValue + (tweenDelta * easing(percentComplete, opts, tweenDelta));
+
+ /* If no value change is occurring, don't proceed with DOM updating. */
+ if (!firstTick && (currentValue === tween.currentValue)) {
+ continue;
+ }
+ }
+
+ tween.currentValue = currentValue;
+
+ /* If we're tweening a fake 'tween' property in order to log transition values, update the one-per-call variable so that
+ it can be passed into the progress callback. */
+ if (property === "tween") {
+ tweenDummyValue = currentValue;
+ } else {
+ /******************
+ Hooks: Part I
+ ******************/
+
+ /* For hooked properties, the newly-updated rootPropertyValueCache is cached onto the element so that it can be used
+ for subsequent hooks in this call that are associated with the same root property. If we didn't cache the updated
+ rootPropertyValue, each subsequent update to the root property in this tick pass would reset the previous hook's
+ updates to rootPropertyValue prior to injection. A nice performance byproduct of rootPropertyValue caching is that
+ subsequently chained animations using the same hookRoot but a different hook can use this cached rootPropertyValue. */
+ if (CSS.Hooks.registered[property]) {
+ var hookRoot = CSS.Hooks.getRoot(property),
+ rootPropertyValueCache = Data(element).rootPropertyValueCache[hookRoot];
+
+ if (rootPropertyValueCache) {
+ tween.rootPropertyValue = rootPropertyValueCache;
+ }
+ }
+
+ /*****************
+ DOM Update
+ *****************/
+
+ /* setPropertyValue() returns an array of the property name and property value post any normalization that may have been performed. */
+ /* Note: To solve an IE<=8 positioning bug, the unit type is dropped when setting a property value of 0. */
+ var adjustedSetData = setPropertyValue(element, /* SET */
+ property,
+ tween.currentValue + (parseFloat(currentValue) === 0 ? "" : tween.unitType),
+ tween.rootPropertyValue,
+ tween.scrollData);
+
+ /*******************
+ Hooks: Part II
+ *******************/
+
+ /* Now that we have the hook's updated rootPropertyValue (the post-processed value provided by adjustedSetData), cache it onto the element. */
+ if (CSS.Hooks.registered[property]) {
+ /* Since adjustedSetData contains normalized data ready for DOM updating, the rootPropertyValue needs to be re-extracted from its normalized form. ?? */
+ if (CSS.Normalizations.registered[hookRoot]) {
+ Data(element).rootPropertyValueCache[hookRoot] = CSS.Normalizations.registered[hookRoot]("extract", null, adjustedSetData[1]);
+ } else {
+ Data(element).rootPropertyValueCache[hookRoot] = adjustedSetData[1];
+ }
+ }
+
+ /***************
+ Transforms
+ ***************/
+
+ /* Flag whether a transform property is being animated so that flushTransformCache() can be triggered once this tick pass is complete. */
+ if (adjustedSetData[0] === "transform") {
+ transformPropertyExists = true;
+ }
+
+ }
+ }
+ }
+
+ /****************
+ mobileHA
+ ****************/
+
+ /* If mobileHA is enabled, set the translate3d transform to null to force hardware acceleration.
+ It's safe to override this property since Animator doesn't actually support its animation (hooks are used in its place). */
+ if (opts.mobileHA) {
+ /* Don't set the null transform hack if we've already done so. */
+ if (Data(element).transformCache.translate3d === undefined) {
+ /* All entries on the transformCache object are later concatenated into a single transform string via flushTransformCache(). */
+ Data(element).transformCache.translate3d = "(0px, 0px, 0px)";
+
+ transformPropertyExists = true;
+ }
+ }
+
+ if (transformPropertyExists) {
+ CSS.flushTransformCache(element);
+ }
+ }
+
+ /* The non-"none" display value is only applied to an element once -- when its associated call is first ticked through.
+ Accordingly, it's set to false so that it isn't re-processed by this call in the next tick. */
+ if (opts.display !== undefined && opts.display !== "none") {
+ this.calls[i][2].display = false;
+ }
+ if (opts.visibility !== undefined && opts.visibility !== "hidden") {
+ this.calls[i][2].visibility = false;
+ }
+
+ /* Pass the elements and the timing data (percentComplete, msRemaining, timeStart, tweenDummyValue) into the progress callback. */
+ if (opts.progress) {
+ opts.progress.call(callContainer[1],
+ callContainer[1],
+ percentComplete,
+ Math.max(0, (timeStart + opts.duration) - timeCurrent),
+ timeStart,
+ tweenDummyValue);
+ }
+
+ /* If this call has finished tweening, pass its index to completeCall() to handle call cleanup. */
+ if (percentComplete === 1) {
+ completeCall(i);
+ }
+ }
+ }
+
+ /* Note: completeCall() sets the isTicking flag to false when the last call on this.calls has completed. */
+ if (this.isTicking) {
+ raf(tick);
+ }
+
+ }
+
+}
+
+
+const flexValues = [ "-webkit-box", "-moz-box", "-ms-flexbox", "-webkit-flex" ];
+
+
+/* Array compacting. Copyright Lo-Dash. MIT License: https://github.com/lodash/lodash/blob/master/LICENSE.txt */
+function compactSparseArray (array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ if (value) {
+ result.push(value);
+ }
+ }
+
+ return result;
+}
+
+function setPropertyValue(element, property, propertyValue, rootPropertyValue, scrollData) {
+ var propertyName = property;
+
+ /* In order to be subjected to call options and element queueing, scroll animation is routed through Velocity as if it were a standard CSS property. */
+ if (property === "scroll") {
+ /* If a container option is present, scroll the container instead of the browser window. */
+ if (scrollData.container) {
+ scrollData.container["scroll" + scrollData.direction] = propertyValue;
+ /* Otherwise, Velocity defaults to scrolling the browser window. */
+ } else {
+ if (scrollData.direction === "Left") {
+ window.scrollTo(propertyValue, scrollData.alternateValue);
+ } else {
+ window.scrollTo(scrollData.alternateValue, propertyValue);
+ }
+ }
+ } else {
+ /* Transforms (translateX, rotateZ, etc.) are applied to a per-element transformCache object, which is manually flushed via flushTransformCache().
+ Thus, for now, we merely cache transforms being SET. */
+ if (CSS.Normalizations.registered[property] && CSS.Normalizations.registered[property]("name", element) === "transform") {
+ /* Perform a normalization injection. */
+ /* Note: The normalization logic handles the transformCache updating. */
+ CSS.Normalizations.registered[property]("inject", element, propertyValue);
+
+ propertyName = "transform";
+ propertyValue = Data(element).transformCache[property];
+ } else {
+ /* Inject hooks. */
+ if (CSS.Hooks.registered[property]) {
+ var hookName = property,
+ hookRoot = CSS.Hooks.getRoot(property);
+
+ /* If a cached rootPropertyValue was not provided, query the DOM for the hookRoot's current value. */
+ rootPropertyValue = rootPropertyValue || CSS.getPropertyValue(element, hookRoot); /* GET */
+
+ propertyValue = CSS.Hooks.injectValue(hookName, propertyValue, rootPropertyValue);
+ property = hookRoot;
+ }
+
+ /* Normalize names and values. */
+ if (CSS.Normalizations.registered[property]) {
+ propertyValue = CSS.Normalizations.registered[property]("inject", element, propertyValue);
+ property = CSS.Normalizations.registered[property]("name", element);
+ }
+
+ /* Assign the appropriate vendor prefix before performing an official style update. */
+ propertyName = CSS.Names.prefixCheck(property)[0];
+
+ /* A try/catch is used for IE<=8, which throws an error when "invalid" CSS values are set, e.g. a negative width.
+ Try/catch is avoided for other browsers since it incurs a performance overhead. */
+ if (IE <= 8) {
+ try {
+ element.style[propertyName] = propertyValue;
+ } catch (error) { if (Velocity.debug) console.log("Browser does not support [" + propertyValue + "] for [" + propertyName + "]"); }
+ /* SVG elements have their dimensional properties (width, height, x, y, cx, etc.) applied directly as attributes instead of as styles. */
+ /* Note: IE8 does not support SVG elements, so it's okay that we skip it for SVG animation. */
+ } else if (Data(element) && Data(element).isSVG && CSS.Names.SVGAttribute(property)) {
+ /* Note: For SVG attributes, vendor-prefixed property names are never used. */
+ /* Note: Not all CSS properties can be animated via attributes, but the browser won't throw an error for unsupported properties. */
+ element.setAttribute(property, propertyValue);
+ } else {
+ element.style[propertyName] = propertyValue;
+ }
+
+ if (Velocity.debug >= 2) console.log("Set " + property + " (" + propertyName + "): " + propertyValue);
+ }
+ }
+
+ /* Return the normalized property name and value in case the caller wants to know how these values were modified before being applied to the DOM. */
+ return [ propertyName, propertyValue ];
+}
+
+
+export let Animator = new GlobalAnimator()
diff --git a/src/collide/collide.js b/src/collide/collide.js
new file mode 100644
index 0000000000..37dcfb04b4
--- /dev/null
+++ b/src/collide/collide.js
@@ -0,0 +1,124 @@
+import {dom} from 'ionic2/util'
+
+
+export const Collide = {
+
+ defaults: {
+ queue: "",
+ duration: 400,
+ easing: 'swing',
+ begin: undefined,
+ complete: undefined,
+ progress: undefined,
+ display: undefined,
+ visibility: undefined,
+ loop: false,
+ delay: false,
+ cacheValues: true
+ },
+
+ data: function(element, key, value) {
+ if (value === undefined) {
+
+ if (key === undefined) {
+ // get data object: Data(element)
+ return element.$collide;
+ }
+
+ if (element.$collide) {
+ // get data by key: Data(element, key)
+ return element.$collide[key];
+ }
+
+ } else if (key !== undefined) {
+ // set data: Data(element, key, value)
+ if (!element.$collide) {
+ element.$collide = {};
+ }
+ element.$collide[key] = value;
+ }
+
+ },
+
+ initData: function(element) {
+ element.$collide = {
+ /* Store whether this is an SVG element, since its properties are retrieved and updated differently than standard HTML elements. */
+ isSVG: dom.isSVG(element),
+
+ /* Keep track of whether the element is currently being animated by Collide.
+ This is used to ensure that property values are not transferred between non-consecutive (stale) calls. */
+ isAnimating: false,
+
+ /* A reference to the element's live computedStyle object. Learn more here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */
+ computedStyle: null,
+
+ /* Tween data is cached for each animation on the element so that data can be passed across calls --
+ in particular, end values are used as subsequent start values in consecutive Collide calls. */
+ tweensContainer: null,
+
+ /* The full root property values of each CSS hook being animated on this element are cached so that:
+ 1) Concurrently-animating hooks sharing the same root can have their root values' merged into one while tweening.
+ 2) Post-hook-injection root values can be transferred over to consecutively chained Collide calls as starting root values. */
+ rootPropertyValueCache: {},
+
+ /* A cache for transform updates, which must be manually flushed via CSS.flushTransformCache(). */
+ transformCache: {}
+ };
+ },
+
+ queue: function (element, type, data) {
+ function $makeArray (arr, results) {
+ let ret = results || [];
+
+ if (arr != null) {
+ [].push.call(ret, arr);
+ }
+
+ return ret;
+ }
+
+ if (!element) {
+ return;
+ }
+
+ type = (type || 'collide') + 'queue';
+
+ var q = this.data(element, type);
+
+ if (!data) {
+ return q || [];
+ }
+
+ if (!q || Array.isArray(data)) {
+ q = this.data(element, type, $makeArray(data));
+
+ } else {
+ q.push(data);
+ }
+
+ return q;
+ },
+
+ dequeue: function (element, type) {
+ type = type || 'collide';
+
+ let queue = this.queue(element, type);
+ let fn = queue.shift();
+
+ if (fn === 'inprogress') {
+ fn = queue.shift();
+ }
+
+ if (fn) {
+ if (type === 'collide') {
+ queue.unshift('inprogress');
+ }
+
+ fn.call(element, () => {
+ this.dequeue(element, type);
+ });
+ }
+ }
+
+}
+
diff --git a/src/collide/css.js b/src/collide/css.js
new file mode 100644
index 0000000000..ec9f1de7cb
--- /dev/null
+++ b/src/collide/css.js
@@ -0,0 +1,898 @@
+/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
+
+import {Collide} from 'ionic2/collide/collide'
+
+const data = Collide.data;
+
+
+/*****************
+ CSS Stack
+*****************/
+
+/* The CSS object is a highly condensed and performant CSS stack that fully replaces jQuery's.
+ It handles the validation, getting, and setting of both standard CSS properties and CSS property hooks. */
+/* Note: A 'CSS' shorthand is aliased so that our code is easier to read. */
+export var CSS = {
+
+
+ /*************
+ CSS RegEx
+ *************/
+
+ RegEx: {
+ isHex: /^#([A-f\d]{3}){1,2}$/i,
+
+ /* Unwrap a property value's surrounding text, e.g. 'rgba(4, 3, 2, 1)' ==> '4, 3, 2, 1' and 'rect(4px 3px 2px 1px)' ==> '4px 3px 2px 1px'. */
+ valueUnwrap: /^[A-z]+\((.*)\)$/i,
+
+ wrappedValueAlreadyExtracted: /[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,
+
+ /* Split a multi-value property into an array of subvalues, e.g. 'rgba(4, 3, 2, 1) 4px 3px 2px 1px' ==> [ 'rgba(4, 3, 2, 1)', '4px', '3px', '2px', '1px' ]. */
+ valueSplit: /([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/ig
+ },
+
+
+ /************
+ CSS Lists
+ ************/
+
+ Lists: {
+ colors: [ 'fill', 'stroke', 'stopColor', 'color', 'backgroundColor', 'borderColor', 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor', 'outlineColor' ],
+ transformsBase: [ 'translateX', 'translateY', 'scale', 'scaleX', 'scaleY', 'skewX', 'skewY', 'rotateZ' ],
+ transforms3D: [ 'transformPerspective', 'translateZ', 'scaleZ', 'rotateX', 'rotateY' ]
+ },
+
+
+ /************
+ CSS Hooks
+ ************/
+
+ /* Hooks allow a subproperty (e.g. 'boxShadowBlur') of a compound-value CSS property
+ (e.g. 'boxShadow: X Y Blur Spread Color') to be animated as if it were a discrete property. */
+ /* Note: Beyond enabling fine-grained property animation, hooking is necessary since Collide only
+ tweens properties with single numeric values; unlike CSS transitions, Collide does not interpolate compound-values. */
+ Hooks: {
+
+ /********************
+ CSS Hook Registration
+ ********************/
+
+ /* Templates are a concise way of indicating which subproperties must be individually registered for each compound-value CSS property. */
+ /* Each template consists of the compound-value's base name, its constituent subproperty names, and those subproperties' default values. */
+ templates: {
+ textShadow: [ 'Color X Y Blur', 'black 0px 0px 0px' ],
+ boxShadow: [ 'Color X Y Blur Spread', 'black 0px 0px 0px 0px' ],
+ clip: [ 'Top Right Bottom Left', '0px 0px 0px 0px' ],
+ backgroundPosition: [ 'X Y', '0% 0%' ],
+ transformOrigin: [ 'X Y Z', '50% 50% 0px' ],
+ perspectiveOrigin: [ 'X Y', '50% 50%' ]
+ },
+
+ /* A 'registered' hook is one that has been converted from its template form into a live,
+ tweenable property. It contains data to associate it with its root property. */
+ registered: {
+ /* Note: A registered hook looks like this ==> textShadowBlur: [ 'textShadow', 3 ],
+ which consists of the subproperty's name, the associated root property's name,
+ and the subproperty's position in the root's value. */
+ },
+
+ /* Convert the templates into individual hooks then append them to the registered object above. */
+ register: function() {
+ /* Color hooks registration: Colors are defaulted to white -- as opposed to black -- since colors that are
+ currently set to 'transparent' default to their respective template below when color-animated,
+ and white is typically a closer match to transparent than black is. An exception is made for text ('color'),
+ which is almost always set closer to black than white. */
+ for (var i = 0; i < CSS.Lists.colors.length; i++) {
+ var rgbComponents = (CSS.Lists.colors[i] === 'color') ? '0 0 0 1' : '255 255 255 1';
+ CSS.Hooks.templates[CSS.Lists.colors[i]] = [ 'Red Green Blue Alpha', rgbComponents ];
+ }
+
+ var rootProperty,
+ hookTemplate,
+ hookNames;
+
+ /* In IE, color values inside compound-value properties are positioned at the end the value instead of at the beginning.
+ Thus, we re-arrange the templates accordingly. */
+ // if (IE) {
+ // for (rootProperty in CSS.Hooks.templates) {
+ // hookTemplate = CSS.Hooks.templates[rootProperty];
+ // hookNames = hookTemplate[0].split(' ');
+
+ // var defaultValues = hookTemplate[1].match(CSS.RegEx.valueSplit);
+
+ // if (hookNames[0] === 'Color') {
+ // // Reposition both the hook's name and its default value to the end of their respective strings.
+ // hookNames.push(hookNames.shift());
+ // defaultValues.push(defaultValues.shift());
+
+ // // Replace the existing template for the hook's root property.
+ // CSS.Hooks.templates[rootProperty] = [ hookNames.join(' '), defaultValues.join(' ') ];
+ // }
+ // }
+ // }
+
+ /* Hook registration. */
+ for (rootProperty in CSS.Hooks.templates) {
+ hookTemplate = CSS.Hooks.templates[rootProperty];
+ hookNames = hookTemplate[0].split(' ');
+
+ for (var i in hookNames) {
+ var fullHookName = rootProperty + hookNames[i],
+ hookPosition = i;
+
+ /* For each hook, register its full name (e.g. textShadowBlur) with its root property (e.g. textShadow)
+ and the hook's position in its template's default value string. */
+ CSS.Hooks.registered[fullHookName] = [ rootProperty, hookPosition ];
+ }
+ }
+ },
+
+
+ /*****************************
+ CSS Hook Injection and Extraction
+ *****************************/
+
+ /* Look up the root property associated with the hook (e.g. return 'textShadow' for 'textShadowBlur'). */
+ /* Since a hook cannot be set directly (the browser won't recognize it), style updating for hooks is routed through the hook's root property. */
+ getRoot: function(property) {
+ var hookData = CSS.Hooks.registered[property];
+
+ if (hookData) {
+ return hookData[0];
+ }
+
+ /* If there was no hook match, return the property name untouched. */
+ return property;
+ },
+
+ /* Convert any rootPropertyValue, null or otherwise, into a space-delimited list of hook values so that
+ the targeted hook can be injected or extracted at its standard position. */
+ cleanRootPropertyValue: function(rootProperty, rootPropertyValue) {
+ /* If the rootPropertyValue is wrapped with 'rgb()', 'clip()', etc., remove the wrapping to normalize the value before manipulation. */
+ if (CSS.RegEx.valueUnwrap.test(rootPropertyValue)) {
+ rootPropertyValue = rootPropertyValue.match(CSS.RegEx.valueUnwrap)[1];
+ }
+
+ /* If rootPropertyValue is a CSS null-value (from which there's inherently no hook value to extract),
+ default to the root's default value as defined in CSS.Hooks.templates. */
+ /* Note: CSS null-values include 'none', 'auto', and 'transparent'. They must be converted into their
+ zero-values (e.g. textShadow: 'none' ==> textShadow: '0px 0px 0px black') for hook manipulation to proceed. */
+ if (CSS.Values.isCSSNullValue(rootPropertyValue)) {
+ rootPropertyValue = CSS.Hooks.templates[rootProperty][1];
+ }
+
+ return rootPropertyValue;
+ },
+
+ /* Extracted the hook's value from its root property's value. This is used to get the starting value of an animating hook. */
+ extractValue: function(fullHookName, rootPropertyValue) {
+ var hookData = CSS.Hooks.registered[fullHookName];
+
+ if (hookData) {
+ var hookRoot = hookData[0],
+ hookPosition = hookData[1];
+
+ rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue);
+
+ /* Split rootPropertyValue into its constituent hook values then grab the desired hook at its standard position. */
+ return rootPropertyValue.toString().match(CSS.RegEx.valueSplit)[hookPosition];
+ }
+
+ /* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */
+ return rootPropertyValue;
+ },
+
+ /* Inject the hook's value into its root property's value. This is used to piece back together the root property
+ once Collide has updated one of its individually hooked values through tweening. */
+ injectValue: function(fullHookName, hookValue, rootPropertyValue) {
+ var hookData = CSS.Hooks.registered[fullHookName];
+
+ if (hookData) {
+ var hookRoot = hookData[0],
+ hookPosition = hookData[1],
+ rootPropertyValueParts,
+ rootPropertyValueUpdated;
+
+ rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue);
+
+ /* Split rootPropertyValue into its individual hook values, replace the targeted value with hookValue,
+ then reconstruct the rootPropertyValue string. */
+ rootPropertyValueParts = rootPropertyValue.toString().match(CSS.RegEx.valueSplit);
+ rootPropertyValueParts[hookPosition] = hookValue;
+ rootPropertyValueUpdated = rootPropertyValueParts.join(' ');
+
+ return rootPropertyValueUpdated;
+ }
+
+ /* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */
+ return rootPropertyValue;
+ }
+ },
+
+
+ /*******************
+ CSS Normalizations
+ *******************/
+
+ /* Normalizations standardize CSS property manipulation by pollyfilling browser-specific implementations (e.g. opacity)
+ and reformatting special properties (e.g. clip, rgba) to look like standard ones. */
+ Normalizations: {
+
+ /* Normalizations are passed a normalization target (either the property's name, its extracted value, or its injected value),
+ the targeted element (which may need to be queried), and the targeted property value. */
+ registered: {
+ clip: function(type, element, propertyValue) {
+ switch (type) {
+
+ case 'name':
+ return 'clip';
+
+ /* Clip needs to be unwrapped and stripped of its commas during extraction. */
+ case 'extract':
+ var extracted;
+
+ /* If Collide also extracted this value, skip extraction. */
+ if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) {
+ extracted = propertyValue;
+
+ } else {
+ /* Remove the 'rect()' wrapper. */
+ extracted = propertyValue.toString().match(CSS.RegEx.valueUnwrap);
+
+ /* Strip off commas. */
+ extracted = extracted ? extracted[1].replace(/,(\s+)?/g, ' ') : propertyValue;
+ }
+
+ return extracted;
+
+ /* Clip needs to be re-wrapped during injection. */
+ case 'inject':
+ return 'rect(' + propertyValue + ')';
+ }
+ },
+
+ blur: function(type, element, propertyValue) {
+ switch (type) {
+
+ case 'name':
+ return '-webkit-filter';
+
+ case 'extract':
+ var extracted = parseFloat(propertyValue);
+
+ /* If extracted is NaN, meaning the value isn't already extracted. */
+ if (!(extracted || extracted === 0)) {
+ var blurComponent = propertyValue.toString().match(/blur\(([0-9]+[A-z]+)\)/i);
+
+ /* If the filter string had a blur component, return just the blur value and unit type. */
+ if (blurComponent) {
+ extracted = blurComponent[1];
+
+ /* If the component doesn't exist, default blur to 0. */
+ } else {
+ extracted = 0;
+ }
+ }
+
+ return extracted;
+
+ /* Blur needs to be re-wrapped during injection. */
+ case 'inject':
+ /* For the blur effect to be fully de-applied, it needs to be set to 'none' instead of 0. */
+ if (!parseFloat(propertyValue)) {
+ return 'none';
+ }
+
+ return 'blur(' + propertyValue + ')';
+ }
+ },
+
+ opacity: function(type, element, propertyValue) {
+ switch (type) {
+ case 'name':
+ return 'opacity';
+
+ case 'extract':
+ return propertyValue;
+
+ case 'inject':
+ return propertyValue;
+ }
+ }
+ },
+
+
+ /*****************************
+ CSS Batched Registrations
+ *****************************/
+
+ /* Note: Batched normalizations extend the CSS.Normalizations.registered object. */
+ register: function() {
+
+ /*****************
+ CSS Batched Registration Transforms
+ *****************/
+
+ /* Transforms are the subproperties contained by the CSS 'transform' property. Transforms must undergo normalization
+ so that they can be referenced in a properties map by their individual names. */
+ /* Note: When transforms are 'set', they are actually assigned to a per-element transformCache. When all transform
+ setting is complete complete, CSS.flushTransformCache() must be manually called to flush the values to the DOM.
+ Transform setting is batched in this way to improve performance: the transform style only needs to be updated
+ once when multiple transform subproperties are being animated simultaneously. */
+
+ for (var i = 0; i < CSS.Lists.transformsBase.length; i++) {
+ /* Wrap the dynamically generated normalization function in a new scope so that transformName's value is
+ paired with its respective function. (Otherwise, all functions would take the final for loop's transformName.) */
+ (function() {
+ var transformName = CSS.Lists.transformsBase[i];
+
+ CSS.Normalizations.registered[transformName] = function(type, element, propertyValue) {
+ var eleData = data(element);
+
+ switch (type) {
+
+ /* The normalized property name is the parent 'transform' property -- the property that is actually set in CSS. */
+ case 'name':
+ return 'transform';
+
+ /* Transform values are cached onto a per-element transformCache object. */
+ case 'extract':
+ /* If this transform has yet to be assigned a value, return its null value. */
+ if (eleData === undefined || eleData.transformCache[transformName] === undefined) {
+ /* Scale CSS.Lists.transformsBase default to 1 whereas all other transform properties default to 0. */
+ return /^scale/i.test(transformName) ? 1 : 0;
+ }
+ /* When transform values are set, they are wrapped in parentheses as per the CSS spec.
+ Thus, when extracting their values (for tween calculations), we strip off the parentheses. */
+ return eleData.transformCache[transformName].replace(/[()]/g, '');
+
+ case 'inject':
+ var invalid = false;
+
+ /* If an individual transform property contains an unsupported unit type, the browser ignores the *entire* transform property.
+ Thus, protect users from themselves by skipping setting for transform values supplied with invalid unit types. */
+ /* Switch on the base transform type; ignore the axis by removing the last letter from the transform's name. */
+ switch (transformName.substr(0, transformName.length - 1)) {
+ /* Whitelist unit types for each transform. */
+ case 'translate':
+ invalid = !/(%|px|em|rem|vw|vh|\d)$/i.test(propertyValue);
+ break;
+
+ /* Since an axis-free 'scale' property is supported as well, a little hack is used here to detect it by chopping off its last letter. */
+ case 'scal':
+ case 'scale':
+ invalid = !/(\d)$/i.test(propertyValue);
+ break;
+
+ case 'skew':
+ invalid = !/(deg|\d)$/i.test(propertyValue);
+ break;
+
+ case 'rotate':
+ invalid = !/(deg|\d)$/i.test(propertyValue);
+ break;
+ }
+
+ if (!invalid) {
+ /* As per the CSS spec, wrap the value in parentheses. */
+ eleData.transformCache[transformName] = '(' + propertyValue + ')';
+ }
+
+ /* Although the value is set on the transformCache object, return the newly-updated value for the calling code to process as normal. */
+ return eleData.transformCache[transformName];
+ }
+ };
+ })();
+ }
+
+ /*************
+ CSS Batched Registration Colors
+ *************/
+
+ /* Since Collide only animates a single numeric value per property, color animation is achieved by hooking the individual RGBA components of CSS color properties.
+ Accordingly, color values must be normalized (e.g. '#ff0000', 'red', and 'rgb(255, 0, 0)' ==> '255 0 0 1') so that their components can be injected/extracted by CSS.Hooks logic. */
+ for (var i = 0; i < CSS.Lists.colors.length; i++) {
+ /* Wrap the dynamically generated normalization function in a new scope so that colorName's value is paired with its respective function.
+ (Otherwise, all functions would take the final for loop's colorName.) */
+ (function() {
+ var colorName = CSS.Lists.colors[i];
+
+ /* Note: In IE<=8, which support rgb but not rgba, color properties are reverted to rgb by stripping off the alpha component. */
+ CSS.Normalizations.registered[colorName] = function(type, element, propertyValue) {
+ switch (type) {
+
+ case 'name':
+ return colorName;
+
+ /* Convert all color values into the rgb format. (Old IE can return hex values and color names instead of rgb/rgba.) */
+ case 'extract':
+ var extracted;
+
+ /* If the color is already in its hookable form (e.g. '255 255 255 1') due to having been previously extracted, skip extraction. */
+ if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) {
+ extracted = propertyValue;
+
+ } else {
+ var converted,
+ colorNames = {
+ black: 'rgb(0, 0, 0)',
+ blue: 'rgb(0, 0, 255)',
+ gray: 'rgb(128, 128, 128)',
+ green: 'rgb(0, 128, 0)',
+ red: 'rgb(255, 0, 0)',
+ white: 'rgb(255, 255, 255)'
+ };
+
+ /* Convert color names to rgb. */
+ if (/^[A-z]+$/i.test(propertyValue)) {
+ if (colorNames[propertyValue] !== undefined) {
+ converted = colorNames[propertyValue]
+
+ } else {
+ /* If an unmatched color name is provided, default to black. */
+ converted = colorNames.black;
+ }
+
+ /* Convert hex values to rgb. */
+ } else if (CSS.RegEx.isHex.test(propertyValue)) {
+ converted = 'rgb(' + CSS.Values.hexToRgb(propertyValue).join(' ') + ')';
+
+ /* If the provided color doesn't match any of the accepted color formats, default to black. */
+ } else if (!(/^rgba?\(/i.test(propertyValue))) {
+ converted = colorNames.black;
+ }
+
+ /* Remove the surrounding 'rgb/rgba()' string then replace commas with spaces and strip
+ repeated spaces (in case the value included spaces to begin with). */
+ extracted = (converted || propertyValue).toString().match(CSS.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g, ' ');
+ }
+
+ /* add a fourth (alpha) component if it's missing and default it to 1 (visible). */
+ if (extracted.split(' ').length === 3) {
+ extracted += ' 1';
+ }
+
+ return extracted;
+
+ case 'inject':
+ /* add a fourth (alpha) component if it's missing and default it to 1 (visible). */
+ if (propertyValue.split(' ').length === 3) {
+ propertyValue += ' 1';
+ }
+
+ /* Re-insert the browser-appropriate wrapper('rgb/rgba()'), insert commas, and strip off decimal units
+ on all values but the fourth (R, G, and B only accept whole numbers). */
+ return 'rgba(' + propertyValue.replace(/\s+/g, ',').replace(/\.(\d)+(?=,)/g, '') + ')';
+ }
+ };
+ })();
+ }
+ }
+ },
+
+
+ /************************
+ CSS Property Names
+ ************************/
+
+ Names: {
+ /* Camelcase a property name into its JavaScript notation (e.g. 'background-color' ==> 'backgroundColor').
+ Camelcasing is used to normalize property names between and across calls. */
+ camelCase: function(property) {
+ return property.replace(/-(\w)/g, function(match, subMatch) {
+ return subMatch.toUpperCase();
+ });
+ },
+
+ /* For SVG elements, some properties (namely, dimensional ones) are GET/SET via the element's HTML attributes (instead of via CSS styles). */
+ SVGAttribute: function(property) {
+ return new RegExp('^(width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2)$', 'i').test(property);
+ },
+
+ /* Determine whether a property should be set with a vendor prefix. */
+ /* If a prefixed version of the property exists, return it. Otherwise, return the original property name.
+ If the property is not at all supported by the browser, return a false flag. */
+ prefixCheck: function(property) {
+ /* If this property has already been checked, return the cached value. */
+ if (CSS.Names.prefixMatches[property]) {
+ return [ CSS.Names.prefixMatches[property], true ];
+
+ } else {
+ for (var i = 0, vendorsLength = vendorPrefixes.length; i < vendorsLength; i++) {
+ var propertyPrefixed;
+
+ if (i === 0) {
+ propertyPrefixed = property;
+
+ } else {
+ /* Capitalize the first letter of the property to conform to JavaScript vendor prefix notation (e.g. webkitFilter). */
+ propertyPrefixed = vendorPrefixes[i] + property.replace(/^\w/, function(match) { return match.toUpperCase(); });
+ }
+
+ /* Check if the browser supports this property as prefixed. */
+ if (typeof prefixElement.style[propertyPrefixed] === 'string') {
+ /* Cache the match. */
+ CSS.Names.prefixMatches[property] = propertyPrefixed;
+
+ return [ propertyPrefixed, true ];
+ }
+ }
+
+ /* If the browser doesn't support this property in any form, include a false flag so that the caller can decide how to proceed. */
+ return [ property, false ];
+ }
+ },
+
+ /* cached property name prefixes */
+ prefixMatches: {}
+ },
+
+
+ /************************
+ CSS Property Values
+ ************************/
+
+ Values: {
+ /* Hex to RGB conversion. Copyright Tim Down: http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb */
+ hexToRgb: function(hex) {
+ var shortformRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
+ longformRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
+ rgbParts;
+
+ hex = hex.replace(shortformRegex, function(m, r, g, b) {
+ return r + r + g + g + b + b;
+ });
+
+ rgbParts = longformRegex.exec(hex);
+
+ return rgbParts ? [ parseInt(rgbParts[1], 16), parseInt(rgbParts[2], 16), parseInt(rgbParts[3], 16) ] : [ 0, 0, 0 ];
+ },
+
+ isCSSNullValue: function(value) {
+ /* The browser defaults CSS values that have not been set to either 0 or one of several possible null-value strings.
+ Thus, we check for both falsiness and these special strings. */
+ /* Null-value checking is performed to default the special strings to 0 (for the sake of tweening) or their hook
+ templates as defined as CSS.Hooks (for the sake of hook injection/extraction). */
+ /* Note: Chrome returns 'rgba(0, 0, 0, 0)' for an undefined color whereas IE returns 'transparent'. */
+ return (value == 0 || /^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(value));
+ },
+
+ /* Retrieve a property's default unit type. Used for assigning a unit type when one is not supplied by the user. */
+ getUnitType: function(property) {
+ if (/^(rotate|skew)/i.test(property)) {
+ return 'deg';
+
+ } else if (/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(property)) {
+ /* The above properties are unitless. */
+ return '';
+ }
+
+ /* Default to px for all other properties. */
+ return 'px';
+ },
+
+ /* HTML elements default to an associated display type when they're not set to display:none. */
+ /* Note: This function is used for correctly setting the non-'none' display value in certain Collide redirects, such as fadeIn/Out. */
+ getDisplayType: function(element) {
+ var tagName = element && element.tagName && element.tagName.toString().toLowerCase();
+
+ if (/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/.test(tagName)) {
+ return 'inline';
+
+ } else if (/^(li)$/.test(tagName)) {
+ return 'list-item';
+
+ } else if (/^(tr)$/.test(tagName)) {
+ return 'table-row';
+
+ } else if (/^(table)$/.test(tagName)) {
+ return 'table';
+
+ } else if (/^(tbody)$/.test(tagName)) {
+ return 'table-row-group';
+ }
+
+ /* Default to 'block' when no match is found. */
+ return 'block';
+ }
+
+ },
+
+
+ /****************************
+ CSS Style Getting & Setting
+ ****************************/
+
+ /* The singular getPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */
+ getPropertyValue: function(element, property, rootPropertyValue, forceStyleLookup) {
+
+ /* Get an element's computed property value. */
+ /* Note: Retrieving the value of a CSS property cannot simply be performed by checking an element's
+ style attribute (which only reflects user-defined values). Instead, the browser must be queried for a property's
+ *computed* value. You can read more about getComputedStyle here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */
+ function computePropertyValue (element, property) {
+ /* When box-sizing isn't set to border-box, height and width style values are incorrectly computed when an
+ element's scrollbars are visible (which expands the element's dimensions). Thus, we defer to the more accurate
+ offsetHeight/Width property, which includes the total dimensions for interior, border, padding, and scrollbar.
+ We subtract border and padding to get the sum of interior + scrollbar. */
+ var computedValue = 0;
+
+ /* Browsers do not return height and width values for elements that are set to display:'none'. Thus, we temporarily
+ toggle display to the element type's default value. */
+ var toggleDisplay = false;
+
+ if (/^(width|height)$/.test(property) && CSS.getPropertyValue(element, 'display') === 0) {
+ toggleDisplay = true;
+ CSS.setPropertyValue(element, 'display', CSS.Values.getDisplayType(element));
+ }
+
+ function revertDisplay () {
+ if (toggleDisplay) {
+ CSS.setPropertyValue(element, 'display', 'none');
+ }
+ }
+
+ if (!forceStyleLookup) {
+ if (property === 'height' && CSS.getPropertyValue(element, 'boxSizing').toString().toLowerCase() !== 'border-box') {
+ var contentBoxHeight = element.offsetHeight - (parseFloat(CSS.getPropertyValue(element, 'borderTopWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'borderBottomWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingTop')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingBottom')) || 0);
+ revertDisplay();
+
+ return contentBoxHeight;
+
+ } else if (property === 'width' && CSS.getPropertyValue(element, 'boxSizing').toString().toLowerCase() !== 'border-box') {
+ var contentBoxWidth = element.offsetWidth - (parseFloat(CSS.getPropertyValue(element, 'borderLeftWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'borderRightWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingLeft')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingRight')) || 0);
+ revertDisplay();
+
+ return contentBoxWidth;
+ }
+ }
+
+ var computedStyle;
+ var eleData = data(element);
+
+ /* For elements that Collide hasn't been called on directly (e.g. when Collide queries the DOM on behalf
+ of a parent of an element its animating), perform a direct getComputedStyle lookup since the object isn't cached. */
+ if (eleData === undefined) {
+ computedStyle = window.getComputedStyle(element, null); /* GET */
+
+ /* If the computedStyle object has yet to be cached, do so now. */
+ } else if (!eleData.computedStyle) {
+ computedStyle = eleData.computedStyle = window.getComputedStyle(element, null); /* GET */
+
+ /* If computedStyle is cached, use it. */
+ } else {
+ computedStyle = eleData.computedStyle;
+ }
+
+ /* IE and Firefox do not return a value for the generic borderColor -- they only return individual values for each border side's color.
+ Also, in all browsers, when border colors aren't all the same, a compound value is returned that isn't setup to parse.
+ So, as a polyfill for querying individual border side colors, we just return the top border's color and animate all borders from that value. */
+ if (property === 'borderColor') {
+ property = 'borderTopColor';
+ }
+
+ computedValue = computedStyle[property];
+
+ /* Fall back to the property's style value (if defined) when computedValue returns nothing,
+ which can happen when the element hasn't been painted. */
+ if (computedValue === '' || computedValue === null) {
+ computedValue = element.style[property];
+ }
+
+ revertDisplay();
+
+ /* For top, right, bottom, and left (TRBL) values that are set to 'auto' on elements of 'fixed' or 'absolute' position,
+ defer to jQuery for converting 'auto' to a numeric value. (For elements with a 'static' or 'relative' position, 'auto' has the same
+ effect as being set to 0, so no conversion is necessary.) */
+ /* An example of why numeric conversion is necessary: When an element with 'position:absolute' has an untouched 'left'
+ property, which reverts to 'auto', left's value is 0 relative to its parent element, but is often non-zero relative
+ to its *containing* (not parent) element, which is the nearest 'position:relative' ancestor or the viewport (and always the viewport in the case of 'position:fixed'). */
+ if (computedValue === 'auto' && /^(top|right|bottom|left)$/i.test(property)) {
+ var position = computePropertyValue(element, 'position'); /* GET */
+
+ /* For absolute positioning, jQuery's $.position() only returns values for top and left;
+ right and bottom will have their 'auto' value reverted to 0. */
+ /* Note: A jQuery object must be created here since jQuery doesn't have a low-level alias for $.position().
+ Not a big deal since we're currently in a GET batch anyway. */
+ if (position === 'fixed' || (position === 'absolute' && /top|left/i.test(property))) {
+ /* Note: jQuery strips the pixel unit from its returned values; we re-add it here to conform with computePropertyValue's behavior. */
+ // TODO!!!!
+ computedValue = $(element).position()[property] + 'px'; /* GET */
+ }
+ }
+
+ return computedValue;
+ }
+
+ var propertyValue;
+
+ /* If this is a hooked property (e.g. 'clipLeft' instead of the root property of 'clip'),
+ extract the hook's value from a normalized rootPropertyValue using CSS.Hooks.extractValue(). */
+ if (CSS.Hooks.registered[property]) {
+ var hook = property,
+ hookRoot = CSS.Hooks.getRoot(hook);
+
+ /* If a cached rootPropertyValue wasn't passed in (which Collide always attempts to do in order to avoid requerying the DOM),
+ query the DOM for the root property's value. */
+ if (rootPropertyValue === undefined) {
+ /* Since the browser is now being directly queried, use the official post-prefixing property name for this lookup. */
+ rootPropertyValue = CSS.getPropertyValue(element, CSS.Names.prefixCheck(hookRoot)[0]); /* GET */
+ }
+
+ /* If this root has a normalization registered, peform the associated normalization extraction. */
+ if (CSS.Normalizations.registered[hookRoot]) {
+ rootPropertyValue = CSS.Normalizations.registered[hookRoot]('extract', element, rootPropertyValue);
+ }
+
+ /* Extract the hook's value. */
+ propertyValue = CSS.Hooks.extractValue(hook, rootPropertyValue);
+
+ /* If this is a normalized property (e.g. 'opacity' becomes 'filter' in <=IE8) or 'translateX' becomes 'transform'),
+ normalize the property's name and value, and handle the special case of transforms. */
+ /* Note: Normalizing a property is mutually exclusive from hooking a property since hook-extracted values are strictly
+ numerical and therefore do not require normalization extraction. */
+ } else if (CSS.Normalizations.registered[property]) {
+ var normalizedPropertyName,
+ normalizedPropertyValue;
+
+ normalizedPropertyName = CSS.Normalizations.registered[property]('name', element);
+
+ /* Transform values are calculated via normalization extraction (see below), which checks against the element's transformCache.
+ At no point do transform GETs ever actually query the DOM; initial stylesheet values are never processed.
+ This is because parsing 3D transform matrices is not always accurate and would bloat our codebase;
+ thus, normalization extraction defaults initial transform values to their zero-values (e.g. 1 for scaleX and 0 for translateX). */
+ if (normalizedPropertyName !== 'transform') {
+ normalizedPropertyValue = computePropertyValue(element, CSS.Names.prefixCheck(normalizedPropertyName)[0]); /* GET */
+
+ /* If the value is a CSS null-value and this property has a hook template, use that zero-value template so that hooks can be extracted from it. */
+ if (CSS.Values.isCSSNullValue(normalizedPropertyValue) && CSS.Hooks.templates[property]) {
+ normalizedPropertyValue = CSS.Hooks.templates[property][1];
+ }
+ }
+
+ propertyValue = CSS.Normalizations.registered[property]('extract', element, normalizedPropertyValue);
+ }
+
+ /* If a (numeric) value wasn't produced via hook extraction or normalization, query the DOM. */
+ if (!/^[\d-]/.test(propertyValue)) {
+ /* For SVG elements, dimensional properties (which SVGAttribute() detects) are tweened via
+ their HTML attribute values instead of their CSS style values. */
+ var eleData = data(element);
+ if (eleData && eleData.isSVG && CSS.Names.SVGAttribute(property)) {
+ /* Since the height/width attribute values must be set manually, they don't reflect computed values.
+ Thus, we use use getBBox() to ensure we always get values for elements with undefined height/width attributes. */
+ if (/^(height|width)$/i.test(property)) {
+ /* Firefox throws an error if .getBBox() is called on an SVG that isn't attached to the DOM. */
+ try {
+ propertyValue = element.getBBox()[property];
+ } catch (error) {
+ propertyValue = 0;
+ }
+
+ /* Otherwise, access the attribute value directly. */
+ } else {
+ propertyValue = element.getAttribute(property);
+ }
+
+ } else {
+ propertyValue = computePropertyValue(element, CSS.Names.prefixCheck(property)[0]); /* GET */
+ }
+ }
+
+ /* Since property lookups are for animation purposes (which entails computing the numeric delta between start and end values),
+ convert CSS null-values to an integer of value 0. */
+ if (CSS.Values.isCSSNullValue(propertyValue)) {
+ propertyValue = 0;
+ }
+
+ return propertyValue;
+ },
+
+ /* The singular setPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */
+ setPropertyValue: function(element, property, propertyValue, rootPropertyValue, scrollData) {
+ var propertyName = property;
+
+ /* In order to be subjected to call options and element queueing, scroll animation is routed through Collie as if it were a standard CSS property. */
+ if (property === 'scroll') {
+ /* If a container option is present, scroll the container instead of the browser window. */
+ if (scrollData.container) {
+ scrollData.container['scroll' + scrollData.direction] = propertyValue;
+
+ /* Otherwise, Collide defaults to scrolling the browser window. */
+ } else {
+ if (scrollData.direction === 'Left') {
+ window.scrollTo(propertyValue, scrollData.alternateValue);
+ } else {
+ window.scrollTo(scrollData.alternateValue, propertyValue);
+ }
+ }
+
+ } else {
+ var eleData = data(element);
+
+ /* Transforms (translateX, rotateZ, etc.) are applied to a per-element transformCache object, which is manually flushed via flushTransformCache().
+ Thus, for now, we merely cache transforms being SET. */
+ if (CSS.Normalizations.registered[property] && CSS.Normalizations.registered[property]('name', element) === 'transform') {
+ /* Perform a normalization injection. */
+ /* Note: The normalization logic handles the transformCache updating. */
+ CSS.Normalizations.registered[property]('inject', element, propertyValue);
+
+ propertyName = 'transform';
+ propertyValue = eleData.transformCache[property];
+
+ } else {
+ /* Inject hooks. */
+ if (CSS.Hooks.registered[property]) {
+ var hookName = property,
+ hookRoot = CSS.Hooks.getRoot(property);
+
+ /* If a cached rootPropertyValue was not provided, query the DOM for the hookRoot's current value. */
+ rootPropertyValue = rootPropertyValue || CSS.getPropertyValue(element, hookRoot); /* GET */
+
+ propertyValue = CSS.Hooks.injectValue(hookName, propertyValue, rootPropertyValue);
+ property = hookRoot;
+ }
+
+ /* Normalize names and values. */
+ if (CSS.Normalizations.registered[property]) {
+ propertyValue = CSS.Normalizations.registered[property]('inject', element, propertyValue);
+ property = CSS.Normalizations.registered[property]('name', element);
+ }
+
+ /* Assign the appropriate vendor prefix before performing an official style update. */
+ propertyName = CSS.Names.prefixCheck(property)[0];
+
+ /* SVG elements have their dimensional properties (width, height, x, y, cx, etc.) applied directly as attributes instead of as styles. */
+
+ if (eleData && eleData.isSVG && CSS.Names.SVGAttribute(property)) {
+ /* Note: For SVG attributes, vendor-prefixed property names are never used. */
+ /* Note: Not all CSS properties can be animated via attributes, but the browser won't throw an error for unsupported properties. */
+ element.setAttribute(property, propertyValue);
+
+ } else {
+ element.style[propertyName] = propertyValue;
+ }
+
+ //if (Collide.debug >= 2) console.log('Set ' + property + ' (' + propertyName + '): ' + propertyValue);
+ }
+ }
+
+ /* Return the normalized property name and value in case the caller wants to know how these values were modified before being applied to the DOM. */
+ return [ propertyName, propertyValue ];
+ },
+
+ /* To increase performance by batching transform updates into a single SET, transforms are not directly applied to an element until flushTransformCache() is called. */
+ /* Note: Collide applies transform properties in the same order that they are chronogically introduced to the element's CSS styles. */
+ flushTransformCache: function(element) {
+ var transformString = '';
+ var transformCache = eleData(element).transformCache;
+
+ var transformValue,
+ perspective;
+
+ /* Transform properties are stored as members of the transformCache object. Concatenate all the members into a string. */
+ for (var transformName in transformCache) {
+ transformValue = transformCache[transformName];
+
+ /* Transform's perspective subproperty must be set first in order to take effect. Store it temporarily. */
+ if (transformName === 'transformPerspective') {
+ perspective = transformValue;
+ return true;
+ }
+
+ transformString += transformName + transformValue + ' ';
+ }
+
+ /* If present, set the perspective subproperty first. */
+ if (perspective) {
+ transformString = 'perspective' + perspective + ' ' + transformString;
+ }
+
+ CSS.setPropertyValue(element, 'transform', transformString);
+ }
+
+};
+
+const vendorPrefixes = [ '', 'Webkit', 'ms' ];
+
+var prefixElement = document.createElement("div");
diff --git a/src/collide/easing.js b/src/collide/easing.js
new file mode 100644
index 0000000000..bfece8799f
--- /dev/null
+++ b/src/collide/easing.js
@@ -0,0 +1,311 @@
+/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
+
+import * as util from 'ionic2/util/util'
+
+
+/**************
+ Easing
+**************/
+
+/* Step easing generator. */
+function generateStep (steps) {
+ return function (p) {
+ return Math.round(p * steps) * (1 / steps);
+ };
+}
+
+/* Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */
+function generateBezier (mX1, mY1, mX2, mY2) {
+ var NEWTON_ITERATIONS = 4,
+ NEWTON_MIN_SLOPE = 0.001,
+ SUBDIVISION_PRECISION = 0.0000001,
+ SUBDIVISION_MAX_ITERATIONS = 10,
+ kSplineTableSize = 11,
+ kSampleStepSize = 1.0 / (kSplineTableSize - 1.0),
+ float32ArraySupported = "Float32Array" in window;
+
+ /* Must contain four arguments. */
+ if (arguments.length !== 4) {
+ return false;
+ }
+
+ /* Arguments must be numbers. */
+ for (var i = 0; i < 4; ++i) {
+ if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) {
+ return false;
+ }
+ }
+
+ /* X values must be in the [0, 1] range. */
+ mX1 = Math.min(mX1, 1);
+ mX2 = Math.min(mX2, 1);
+ mX1 = Math.max(mX1, 0);
+ mX2 = Math.max(mX2, 0);
+
+ var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
+
+ function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
+ function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
+ function C (aA1) { return 3.0 * aA1; }
+
+ function calcBezier (aT, aA1, aA2) {
+ return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
+ }
+
+ function getSlope (aT, aA1, aA2) {
+ return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
+ }
+
+ function newtonRaphsonIterate (aX, aGuessT) {
+ for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
+ var currentSlope = getSlope(aGuessT, mX1, mX2);
+
+ if (currentSlope === 0.0) return aGuessT;
+
+ var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
+ aGuessT -= currentX / currentSlope;
+ }
+
+ return aGuessT;
+ }
+
+ function calcSampleValues () {
+ for (var i = 0; i < kSplineTableSize; ++i) {
+ mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
+ }
+ }
+
+ function binarySubdivide (aX, aA, aB) {
+ var currentX, currentT, i = 0;
+
+ do {
+ currentT = aA + (aB - aA) / 2.0;
+ currentX = calcBezier(currentT, mX1, mX2) - aX;
+ if (currentX > 0.0) {
+ aB = currentT;
+ } else {
+ aA = currentT;
+ }
+ } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
+
+ return currentT;
+ }
+
+ function getTForX (aX) {
+ var intervalStart = 0.0,
+ currentSample = 1,
+ lastSample = kSplineTableSize - 1;
+
+ for (; currentSample != lastSample && mSampleValues[currentSample] <= aX; ++currentSample) {
+ intervalStart += kSampleStepSize;
+ }
+
+ --currentSample;
+
+ var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample+1] - mSampleValues[currentSample]),
+ guessForT = intervalStart + dist * kSampleStepSize,
+ initialSlope = getSlope(guessForT, mX1, mX2);
+
+ if (initialSlope >= NEWTON_MIN_SLOPE) {
+ return newtonRaphsonIterate(aX, guessForT);
+ } else if (initialSlope == 0.0) {
+ return guessForT;
+ } else {
+ return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
+ }
+ }
+
+ var _precomputed = false;
+
+ function precompute() {
+ _precomputed = true;
+ if (mX1 != mY1 || mX2 != mY2) calcSampleValues();
+ }
+
+ var f = function (aX) {
+ if (!_precomputed) precompute();
+ if (mX1 === mY1 && mX2 === mY2) return aX;
+ if (aX === 0) return 0;
+ if (aX === 1) return 1;
+
+ return calcBezier(getTForX(aX), mY1, mY2);
+ };
+
+ f.getControlPoints = function() { return [{ x: mX1, y: mY1 }, { x: mX2, y: mY2 }]; };
+
+ var str = "generateBezier(" + [mX1, mY1, mX2, mY2] + ")";
+ f.toString = function () { return str; };
+
+ return f;
+}
+
+/* Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */
+/* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass
+ then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */
+var generateSpringRK4 = (function () {
+ function springAccelerationForState (state) {
+ return (-state.tension * state.x) - (state.friction * state.v);
+ }
+
+ function springEvaluateStateWithDerivative (initialState, dt, derivative) {
+ var state = {
+ x: initialState.x + derivative.dx * dt,
+ v: initialState.v + derivative.dv * dt,
+ tension: initialState.tension,
+ friction: initialState.friction
+ };
+
+ return { dx: state.v, dv: springAccelerationForState(state) };
+ }
+
+ function springIntegrateState (state, dt) {
+ var a = {
+ dx: state.v,
+ dv: springAccelerationForState(state)
+ },
+ b = springEvaluateStateWithDerivative(state, dt * 0.5, a),
+ c = springEvaluateStateWithDerivative(state, dt * 0.5, b),
+ d = springEvaluateStateWithDerivative(state, dt, c),
+ dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx),
+ dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv);
+
+ state.x = state.x + dxdt * dt;
+ state.v = state.v + dvdt * dt;
+
+ return state;
+ }
+
+ return function springRK4Factory (tension, friction, duration) {
+
+ var initState = {
+ x: -1,
+ v: 0,
+ tension: null,
+ friction: null
+ },
+ path = [0],
+ time_lapsed = 0,
+ tolerance = 1 / 10000,
+ DT = 16 / 1000,
+ have_duration, dt, last_state;
+
+ tension = parseFloat(tension) || 500;
+ friction = parseFloat(friction) || 20;
+ duration = duration || null;
+
+ initState.tension = tension;
+ initState.friction = friction;
+
+ have_duration = duration !== null;
+
+ /* Calculate the actual time it takes for this animation to complete with the provided conditions. */
+ if (have_duration) {
+ /* Run the simulation without a duration. */
+ time_lapsed = springRK4Factory(tension, friction);
+ /* Compute the adjusted time delta. */
+ dt = time_lapsed / duration * DT;
+ } else {
+ dt = DT;
+ }
+
+ while (true) {
+ /* Next/step function .*/
+ last_state = springIntegrateState(last_state || initState, dt);
+ /* Store the position. */
+ path.push(1 + last_state.x);
+ time_lapsed += 16;
+ /* If the change threshold is reached, break. */
+ if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) {
+ break;
+ }
+ }
+
+ /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the
+ computed path and returns a snapshot of the position according to a given percentComplete. */
+ return !have_duration ? time_lapsed : function(percentComplete) { return path[ (percentComplete * (path.length - 1)) | 0 ]; };
+ };
+}());
+
+
+/* default easings. */
+let easings = {
+ linear: function(p) { return p; },
+ swing: function(p) { return 0.5 - Math.cos( p * Math.PI ) / 2 },
+ spring: function(p) { return 1 - (Math.cos(p * 4.5 * Math.PI) * Math.exp(-p * 6)); }
+};
+
+
+/* CSS3 and Robert Penner easings. */
+(function() {
+
+ let penner = [
+ [ "ease", [ 0.25, 0.1, 0.25, 1.0 ] ],
+ [ "ease-in", [ 0.42, 0.0, 1.00, 1.0 ] ],
+ [ "ease-out", [ 0.00, 0.0, 0.58, 1.0 ] ],
+ [ "ease-in-out", [ 0.42, 0.0, 0.58, 1.0 ] ],
+ [ "easeInSine", [ 0.47, 0, 0.745, 0.715 ] ],
+ [ "easeOutSine", [ 0.39, 0.575, 0.565, 1 ] ],
+ [ "easeInOutSine", [ 0.445, 0.05, 0.55, 0.95 ] ],
+ [ "easeInQuad", [ 0.55, 0.085, 0.68, 0.53 ] ],
+ [ "easeOutQuad", [ 0.25, 0.46, 0.45, 0.94 ] ],
+ [ "easeInOutQuad", [ 0.455, 0.03, 0.515, 0.955 ] ],
+ [ "easeInCubic", [ 0.55, 0.055, 0.675, 0.19 ] ],
+ [ "easeOutCubic", [ 0.215, 0.61, 0.355, 1 ] ],
+ [ "easeInOutCubic", [ 0.645, 0.045, 0.355, 1 ] ],
+ [ "easeInQuart", [ 0.895, 0.03, 0.685, 0.22 ] ],
+ [ "easeOutQuart", [ 0.165, 0.84, 0.44, 1 ] ],
+ [ "easeInOutQuart", [ 0.77, 0, 0.175, 1 ] ],
+ [ "easeInQuint", [ 0.755, 0.05, 0.855, 0.06 ] ],
+ [ "easeOutQuint", [ 0.23, 1, 0.32, 1 ] ],
+ [ "easeInOutQuint", [ 0.86, 0, 0.07, 1 ] ],
+ [ "easeInExpo", [ 0.95, 0.05, 0.795, 0.035 ] ],
+ [ "easeOutExpo", [ 0.19, 1, 0.22, 1 ] ],
+ [ "easeInOutExpo", [ 1, 0, 0, 1 ] ],
+ [ "easeInCirc", [ 0.6, 0.04, 0.98, 0.335 ] ],
+ [ "easeOutCirc", [ 0.075, 0.82, 0.165, 1 ] ],
+ [ "easeInOutCirc", [ 0.785, 0.135, 0.15, 0.86 ] ]
+ ];
+
+ for (let x = 0; x < penner.length; x++) {
+ easings[ penner[x][0] ] = generateBezier.apply(null, penner[x][1]);
+ }
+
+})();
+
+
+/* Determine the appropriate easing type given an easing input. */
+export function getEasing(value, duration) {
+ let easing = value;
+
+ /* The easing option can either be a string that references a pre-registered easing,
+ or it can be a two-/four-item array of integers to be converted into a bezier/spring function. */
+ if (util.isString(value)) {
+ /* Ensure that the easing has been assigned to standard easings object. */
+ if (!easings[value]) {
+ easing = false;
+ }
+
+ } else if (util.isArray(value) && value.length === 1) {
+ easing = generateStep.apply(null, value);
+
+ } else if (util.isArray(value) && value.length === 2) {
+ /* springRK4 must be passed the animation's duration. */
+ /* Note: If the springRK4 array contains non-numbers, generateSpringRK4() returns an easing
+ function generated with default tension and friction values. */
+ easing = generateSpringRK4.apply(null, value.concat([ duration ]));
+
+ } else if (util.isArray(value) && value.length === 4) {
+ /* Note: If the bezier array contains non-numbers, generateBezier() returns false. */
+ easing = generateBezier.apply(null, value);
+
+ } else {
+ easing = false;
+ }
+
+ /* Revert to the fall back to "swing" */
+ if (easing === false) {
+ easing = 'swing';
+ }
+
+ return easing;
+}
diff --git a/src/collide/init-transition.js b/src/collide/init-transition.js
new file mode 100644
index 0000000000..b565511f46
--- /dev/null
+++ b/src/collide/init-transition.js
@@ -0,0 +1,233 @@
+/* Ported from Collide.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
+
+import {Collide} from 'ionic2/collide/collide'
+import {Animator} from 'ionic2/collide/animator'
+
+
+export function initTransition(action, elements, options, propertiesMap) {
+
+ elements = elements && !elements.length ? [elements] : elements
+
+ if (!elements) {
+ return;
+ }
+
+ /* The length of the element set (in the form of a nodeList or an array of elements) is defaulted to 1 in case a
+ single raw DOM element is passed in (which doesn't contain a length property). */
+ var elementsLength = elements.length;
+ var elementsIndex = 0;
+ var eleData;
+
+
+ /*********************
+ Action Detection
+ *********************/
+
+ switch (action) {
+
+ case 'stop':
+ /******************
+ Action: Stop
+ *******************/
+
+ /* Clear the currently-active delay on each targeted element. */
+ for (var x = 0; x < elements.length; x++) {
+ eleData = Collide.data(elements[x]);
+
+ if (eleData && eleData.delayTimer) {
+ /* Stop the timer from triggering its cached next() function. */
+ clearTimeout(eleData.delayTimer.setTimeout);
+
+ /* Manually call the next() function so that the subsequent queue items can progress. */
+ if (eleData.delayTimer.next) {
+ eleData.delayTimer.next();
+ }
+
+ delete eleData.delayTimer;
+ }
+ }
+
+
+ var callsToStop = [];
+ var activeCall;
+
+ /* When the stop action is triggered, the elements' currently active call is immediately stopped. The active call might have
+ been applied to multiple elements, in which case all of the call's elements will be stopped. When an element
+ is stopped, the next item in its animation queue is immediately triggered. */
+ /* An additional argument may be passed in to clear an element's remaining queued calls. Either true (which defaults to the 'fx' queue)
+ or a custom queue string can be passed in. */
+ /* Note: The stop command runs prior to Collide's Queueing phase since its behavior is intended to take effect *immediately*,
+ regardless of the element's current queue state. */
+
+ /* Iterate through every active call. */
+ for (var x = 0, callsLength = Animator.calls.length; x < callsLength; x++) {
+
+ /* Inactive calls are set to false by the logic inside completeCall(). Skip them. */
+ activeCall = Animator.calls[x];
+ if (activeCall) {
+
+ /* Iterate through the active call's targeted elements. */
+
+ $.each(activeCall[1], function(k, activeElement) {
+ /* If true was passed in as a secondary argument, clear absolutely all calls on this element. Otherwise, only
+ clear calls associated with the relevant queue. */
+ /* Call stopping logic works as follows:
+ - options === true --> stop current default queue calls (and queue:false calls), including remaining queued ones.
+ - options === undefined --> stop current queue:'' call and all queue:false calls.
+ - options === false --> stop only queue:false calls.
+ - options === 'custom' --> stop current queue:'custom' call, including remaining queued ones (there is no functionality to only clear the currently-running queue:'custom' call). */
+ var queueName = (options === undefined) ? '' : options;
+
+ if (queueName !== true && (activeCall[2].queue !== queueName) && !(options === undefined && activeCall[2].queue === false)) {
+ return true;
+ }
+
+ /* Iterate through the calls targeted by the stop command. */
+ $.each(elements, function(l, element) {
+ /* Check that this call was applied to the target element. */
+ if (element === activeElement) {
+ /* Optionally clear the remaining queued calls. */
+ if (options === true || Type.isString(options)) {
+ /* Iterate through the items in the element's queue. */
+ $.each($.queue(element, Type.isString(options) ? options : ''), function(_, item) {
+ /* The queue array can contain an 'inprogress' string, which we skip. */
+ if (Type.isFunction(item)) {
+ /* Pass the item's callback a flag indicating that we want to abort from the queue call.
+ (Specifically, the queue will resolve the call's associated promise then abort.) */
+ item(null, true);
+ }
+ });
+
+ /* Clearing the $.queue() array is achieved by resetting it to []. */
+ $.queue(element, Type.isString(options) ? options : '', []);
+ }
+
+ if (propertiesMap === 'stop') {
+ /* Since 'reverse' uses cached start values (the previous call's endValues), these values must be
+ changed to reflect the final value that the elements were actually tweened to. */
+ /* Note: If only queue:false animations are currently running on an element, it won't have a tweensContainer
+ object. Also, queue:false animations can't be reversed. */
+ if (Data(element) && Data(element).tweensContainer && queueName !== false) {
+ $.each(Data(element).tweensContainer, function(m, activeTween) {
+ activeTween.endValue = activeTween.currentValue;
+ });
+ }
+
+ callsToStop.push(i);
+ } else if (propertiesMap === 'finish') {
+ /* To get active tweens to finish immediately, we forcefully shorten their durations to 1ms so that
+ they finish upon the next rAf tick then proceed with normal call completion logic. */
+ activeCall[2].duration = 1;
+ }
+ }
+ });
+ });
+
+ }
+
+ } // END: for (var x = 0, l = Animator.calls.length; x < l; x++) {
+
+ /* Prematurely call completeCall() on each matched active call. Pass an additional flag for 'stop' to indicate
+ that the complete callback and display:none setting should be skipped since we're completing prematurely. */
+ if (propertiesMap === 'stop') {
+ $.each(callsToStop, function(i, j) {
+ completeCall(j, true);
+ });
+
+ if (promiseData.promise) {
+ /* Immediately resolve the promise associated with this stop call since stop runs synchronously. */
+ promiseData.resolver(elements);
+ }
+ }
+
+ /* Since we're stopping, and not proceeding with queueing, exit out of Collide. */
+ return getChain();
+
+ case 'start':
+ /* Treat a non-empty plain object as a literal properties map. */
+ if ($.isPlainObject(propertiesMap) && !Type.isEmptyObject(propertiesMap)) {
+ action = 'start';
+
+ /****************
+ Redirects
+ ****************/
+
+ /* Check if a string matches a registered redirect (see Redirects above). */
+ } else if (Type.isString(propertiesMap) && Collide.Redirects[propertiesMap]) {
+ var opts = $.extend({}, options),
+ durationOriginal = opts.duration,
+ delayOriginal = opts.delay || 0;
+
+ /* If the backwards option was passed in, reverse the element set so that elements animate from the last to the first. */
+ if (opts.backwards === true) {
+ elements = $.extend(true, [], elements).reverse();
+ }
+
+ /* Individually trigger the redirect for each element in the set to prevent users from having to handle iteration logic in their redirect. */
+ $.each(elements, function(elementIndex, element) {
+ /* If the stagger option was passed in, successively delay each element by the stagger value (in ms). Retain the original delay value. */
+ if (parseFloat(opts.stagger)) {
+ opts.delay = delayOriginal + (parseFloat(opts.stagger) * elementIndex);
+ } else if (Type.isFunction(opts.stagger)) {
+ opts.delay = delayOriginal + opts.stagger.call(element, elementIndex, elementsLength);
+ }
+
+ /* If the drag option was passed in, successively increase/decrease (depending on the presense of opts.backwards)
+ the duration of each element's animation, using floors to prevent producing very short durations. */
+ if (opts.drag) {
+ /* Default the duration of UI pack effects (callouts and transitions) to 1000ms instead of the usual default duration of 400ms. */
+ opts.duration = parseFloat(durationOriginal) || (/^(callout|transition)/.test(propertiesMap) ? 1000 : DURATION_DEFAULT);
+
+ /* For each element, take the greater duration of: A) animation completion percentage relative to the original duration,
+ B) 75% of the original duration, or C) a 200ms fallback (in case duration is already set to a low value).
+ The end result is a baseline of 75% of the redirect's duration that increases/decreases as the end of the element set is approached. */
+ opts.duration = Math.max(opts.duration * (opts.backwards ? 1 - elementIndex/elementsLength : (elementIndex + 1) / elementsLength), opts.duration * 0.75, 200);
+ }
+
+ /* Pass in the call's opts object so that the redirect can optionally extend it. It defaults to an empty object instead of null to
+ reduce the opts checking logic required inside the redirect. */
+ Collide.Redirects[propertiesMap].call(element, element, opts || {}, elementIndex, elementsLength, elements, promiseData.promise ? promiseData : undefined);
+ });
+
+ /* Since the animation logic resides within the redirect's own code, abort the remainder of this call.
+ (The performance overhead up to this point is virtually non-existant.) */
+ /* Note: The jQuery call chain is kept intact by returning the complete element set. */
+ return getChain();
+ } else {
+ var abortError = 'Collide: First argument (' + propertiesMap + ') was not a property map, a known action, or a registered redirect. Aborting.';
+
+ if (promiseData.promise) {
+ promiseData.rejecter(new Error(abortError));
+ } else {
+ console.log(abortError);
+ }
+
+ return getChain();
+ }
+ }
+
+ /**************************
+ Call-Wide Variables
+ **************************/
+
+ /* A container for CSS unit conversion ratios (e.g. %, rem, and em ==> px) that is used to cache ratios across all elements
+ being animated in a single Collide call. Calculating unit ratios necessitates DOM querying and updating, and is therefore
+ avoided (via caching) wherever possible. This container is call-wide instead of page-wide to avoid the risk of using stale
+ conversion metrics across Collide animations that are not immediately consecutively chained. */
+ var callUnitConversionData = {
+ lastParent: null,
+ lastPosition: null,
+ lastFontSize: null,
+ lastPercentToPxWidth: null,
+ lastPercentToPxHeight: null,
+ lastEmToPx: null,
+ remToPx: null,
+ vwToPx: null,
+ vhToPx: null
+ };
+
+ /* A container for all the ensuing tween data and metadata associated with this call. This container gets pushed to the page-wide
+ Collide.State.calls array that is processed during animation ticking. */
+ var call = [];
+
+};
diff --git a/src/collide/process-transition.js b/src/collide/process-transition.js
new file mode 100644
index 0000000000..fea7de0af0
--- /dev/null
+++ b/src/collide/process-transition.js
@@ -0,0 +1,920 @@
+/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
+
+import * as util from 'ionic2/util/util'
+import {Collide} from 'ionic2/collide/collide'
+import {CSS} from 'ionic2/collide/css'
+import {getEasing} from 'ionic2/collide/easing'
+
+
+/*********************
+ Element Processing
+*********************/
+
+/* Element processing consists of three parts -- data processing that cannot go stale and data processing that *can* go stale (i.e. third-party style modifications):
+ 1) Pre-Queueing: Element-wide variables, including the element's data storage, are instantiated. Call options are prepared. If triggered, the Stop action is executed.
+ 2) Queueing: The logic that runs once this call has reached its point of execution in the element's Collide.queue() stack. Most logic is placed here to avoid risking it becoming stale.
+ 3) Pushing: Consolidation of the tween data followed by its push onto the global in-progress calls container.
+*/
+
+export function processTransition(action, elements, options, propertiesMap) {
+ var data = Collide.data;
+
+ for (var elementsIndex = 0; elementsIndex < elements.length; elementsIndex++) {
+ var element = elements[elementsIndex];
+
+ /*************************
+ Part I: Pre-Queueing
+ *************************/
+
+ /***************************
+ Element-Wide Variables
+ ***************************/
+
+ /* The runtime opts object is the extension of the current call's options and Collide's page-wide option defaults. */
+ var opts = util.extend({}, Collide.defaults, options);
+
+ /* A container for the processed data associated with each property in the propertyMap.
+ (Each property in the map produces its own 'tween'.) */
+ var tweensContainer = {};
+ var elementUnitConversionData = null;
+
+
+ /******************
+ Element Init
+ ******************/
+
+ if (data(element) === undefined) {
+ Collide.initData(element);
+ }
+
+
+ /******************
+ Option: Delay
+ ******************/
+
+ /* Since queue:false doesn't respect the item's existing queue, we avoid injecting its delay here (it's set later on). */
+ /* Note: Collide rolls its own delay function since jQuery doesn't have a utility alias for $.fn.delay()
+ (and thus requires jQuery element creation, which we avoid since its overhead includes DOM querying). */
+ if (parseFloat(opts.delay) && opts.queue !== false) {
+ Collide.queue(element, opts.queue, function(next) {
+ // This is a flag used to indicate to the upcoming completeCall() function that this queue entry was initiated by Collide.
+ // See completeCall() for further details.
+ Collide.CollideQueueEntryFlag = true;
+
+ // The ensuing queue item (which is assigned to the 'next' argument that Collide.queue() automatically passes in) will be triggered after a setTimeout delay.
+ // The setTimeout is stored so that it can be subjected to clearTimeout() if this animation is prematurely stopped via Collide's 'stop' command.
+ data(element).delayTimer = {
+ setTimeout: setTimeout(next, parseFloat(opts.delay)),
+ next: next
+ };
+ });
+ }
+
+
+ /*********************
+ Option: Duration
+ *********************/
+
+ opts.duration = parseFloat(opts.duration) || 1;
+
+
+ /*******************
+ Option: Easing
+ *******************/
+
+ opts.easing = getEasing(opts.easing, opts.duration);
+
+
+ /**********************
+ Option: Callbacks
+ **********************/
+
+ /* Callbacks must functions. Otherwise, default to null. */
+ if (opts.begin && typeof opts.begin !== 'function') {
+ opts.begin = null;
+ }
+
+ if (opts.progress && typeof opts.progress !== 'function') {
+ opts.progress = null;
+ }
+
+ if (opts.complete && typeof opts.complete !== 'function') {
+ opts.complete = null;
+ }
+
+
+ /*********************************
+ Option: Display & Visibility
+ *********************************/
+
+ /* Refer to Collide's documentation (CollideJS.org/#displayAndVisibility) for a description of the display and visibility options' behavior. */
+ /* Note: We strictly check for undefined instead of falsiness because display accepts an empty string value. */
+ if (opts.display !== undefined && opts.display !== null) {
+ opts.display = opts.display.toString().toLowerCase();
+
+ /* Users can pass in a special 'auto' value to instruct Collide to set the element to its default display value. */
+ if (opts.display === 'auto') {
+ opts.display = CSS.getDisplayType(element);
+ }
+ }
+
+ if (opts.visibility !== undefined && opts.visibility !== null) {
+ opts.visibility = opts.visibility.toString().toLowerCase();
+ }
+
+
+ /***********************
+ Part II: Queueing
+ ***********************/
+
+ /* When a set of elements is targeted by a Collide call, the set is broken up and each element has the current Collide call individually queued onto it.
+ In this way, each element's existing queue is respected; some elements may already be animating and accordingly should not have this current Collide call triggered immediately. */
+ /* In each queue, tween data is processed for each animating property then pushed onto the call-wide calls array. When the last element in the set has had its tweens processed,
+ the call array is pushed to Collide.State.calls for live processing by the requestAnimationFrame tick. */
+ function buildQueue(next) {
+
+
+ /*******************
+ Option: Begin
+ *******************/
+
+ /* The begin callback is fired once per call -- not once per elemenet -- and is passed the full raw DOM element set as both its context and its first argument. */
+ if (opts.begin && elementsIndex === 0) {
+ /* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Collide itself. */
+ try {
+ opts.begin.call(elements, elements);
+ } catch (error) {
+ setTimeout(function() { throw error; });
+ }
+ }
+
+
+ /*****************************************
+ Tween Data Construction (for Scroll)
+ *****************************************/
+
+ /* Note: In order to be subjected to chaining and animation options, scroll's tweening is routed through Collide as if it were a standard CSS property animation. */
+ if (action === 'scroll') {
+ /* The scroll action uniquely takes an optional 'offset' option -- specified in pixels -- that offsets the targeted scroll position. */
+ var scrollDirection = (/^x$/i.test(opts.axis) ? 'Left' : 'Top'),
+ scrollOffset = parseFloat(opts.offset) || 0,
+ scrollPositionCurrent,
+ scrollPositionCurrentAlternate,
+ scrollPositionEnd;
+
+ /* Scroll also uniquely takes an optional 'container' option, which indicates the parent element that should be scrolled --
+ as opposed to the browser window itself. This is useful for scrolling toward an element that's inside an overflowing parent element. */
+ if (opts.container) {
+ /* Ensure that either a jQuery object or a raw DOM element was passed in. */
+ if (util.isWrapped(opts.container) || util.isNode(opts.container)) {
+ /* Extract the raw DOM element from the jQuery wrapper. */
+ opts.container = opts.container[0] || opts.container;
+ /* Note: Unlike other properties in Collide, the browser's scroll position is never cached since it so frequently changes
+ (due to the user's natural interaction with the page). */
+ scrollPositionCurrent = opts.container['scroll' + scrollDirection]; /* GET */
+
+ /* $.position() values are relative to the container's currently viewable area (without taking into account the container's true dimensions
+ -- say, for example, if the container was not overflowing). Thus, the scroll end value is the sum of the child element's position *and*
+ the scroll container's current scroll position. */
+ scrollPositionEnd = (scrollPositionCurrent + $(element).position()[scrollDirection.toLowerCase()]) + scrollOffset; /* GET */
+
+ } else {
+ /* If a value other than a jQuery object or a raw DOM element was passed in, default to null so that this option is ignored. */
+ opts.container = null;
+ }
+
+ } else {
+ /* If the window itself is being scrolled -- not a containing element -- perform a live scroll position lookup using
+ the appropriate cached property names (which differ based on browser type). */
+ scrollPositionCurrent = Collide.State.scrollAnchor[Collide.State['scrollProperty' + scrollDirection]]; /* GET */
+ /* When scrolling the browser window, cache the alternate axis's current value since window.scrollTo() doesn't let us change only one value at a time. */
+ scrollPositionCurrentAlternate = Collide.State.scrollAnchor[Collide.State['scrollProperty' + (scrollDirection === 'Left' ? 'Top' : 'Left')]]; /* GET */
+
+ /* Unlike $.position(), $.offset() values are relative to the browser window's true dimensions -- not merely its currently viewable area --
+ and therefore end values do not need to be compounded onto current values. */
+ scrollPositionEnd = $(element).offset()[scrollDirection.toLowerCase()] + scrollOffset; /* GET */
+ }
+
+ /* Since there's only one format that scroll's associated tweensContainer can take, we create it manually. */
+ tweensContainer = {
+ scroll: {
+ rootPropertyValue: false,
+ startValue: scrollPositionCurrent,
+ currentValue: scrollPositionCurrent,
+ endValue: scrollPositionEnd,
+ unitType: '',
+ easing: opts.easing,
+ scrollData: {
+ container: opts.container,
+ direction: scrollDirection,
+ alternateValue: scrollPositionCurrentAlternate
+ }
+ },
+ element: element
+ };
+
+
+ /******************************************
+ Tween Data Construction (for Reverse)
+ ******************************************/
+
+ /* Reverse acts like a 'start' action in that a property map is animated toward. The only difference is
+ that the property map used for reverse is the inverse of the map used in the previous call. Thus, we manipulate
+ the previous call to construct our new map: use the previous map's end values as our new map's start values. Copy over all other data. */
+ /* Note: Reverse can be directly called via the 'reverse' parameter, or it can be indirectly triggered via the loop option. (Loops are composed of multiple reverses.) */
+ /* Note: Reverse calls do not need to be consecutively chained onto a currently-animating element in order to operate on cached values;
+ there is no harm to reverse being called on a potentially stale data cache since reverse's behavior is simply defined
+ as reverting to the element's values as they were prior to the previous *Collide* call. */
+ } else if (action === 'reverse') {
+ /* Abort if there is no prior animation data to reverse to. */
+ if (!data(element).tweensContainer) {
+ /* Dequeue the element so that this queue entry releases itself immediately, allowing subsequent queue entries to run. */
+ Collide.dequeue(element, opts.queue);
+ return;
+
+ } else {
+ /*********************
+ Options Parsing
+ *********************/
+
+ /* If the element was hidden via the display option in the previous call,
+ revert display to 'auto' prior to reversal so that the element is visible again. */
+ if (data(element).opts.display === 'none') {
+ data(element).opts.display = 'auto';
+ }
+
+ if (data(element).opts.visibility === 'hidden') {
+ data(element).opts.visibility = 'visible';
+ }
+
+ /* If the loop option was set in the previous call, disable it so that 'reverse' calls aren't recursively generated.
+ Further, remove the previous call's callback options; typically, users do not want these to be refired. */
+ data(element).opts.loop = false;
+ data(element).opts.begin = null;
+ data(element).opts.complete = null;
+
+ /* Since we're extending an opts object that has already been extended with the defaults options object,
+ we remove non-explicitly-defined properties that are auto-assigned values. */
+ if (!options.easing) {
+ delete opts.easing;
+ }
+
+ if (!options.duration) {
+ delete opts.duration;
+ }
+
+ /* The opts object used for reversal is an extension of the options object optionally passed into this
+ reverse call plus the options used in the previous Collide call. */
+ opts = util.extend({}, data(element).opts, opts);
+
+ /*************************************
+ Tweens Container Reconstruction
+ *************************************/
+
+ /* Create a deepy copy (indicated via the true flag) of the previous call's tweensContainer. */
+ var lastTweensContainer = util.extend(true, {}, data(element).tweensContainer);
+
+ /* Manipulate the previous tweensContainer by replacing its end values and currentValues with its start values. */
+ for (var lastTween in lastTweensContainer) {
+ /* In addition to tween data, tweensContainers contain an element property that we ignore here. */
+ if (lastTween !== 'element') {
+ var lastStartValue = lastTweensContainer[lastTween].startValue;
+
+ lastTweensContainer[lastTween].startValue = lastTweensContainer[lastTween].currentValue = lastTweensContainer[lastTween].endValue;
+ lastTweensContainer[lastTween].endValue = lastStartValue;
+
+ /* Easing is the only option that embeds into the individual tween data (since it can be defined on a per-property basis).
+ Accordingly, every property's easing value must be updated when an options object is passed in with a reverse call.
+ The side effect of this extensibility is that all per-property easing values are forcefully reset to the new value. */
+ if (!util.isEmptyObject(options)) {
+ lastTweensContainer[lastTween].easing = opts.easing;
+ }
+ }
+ }
+
+ tweensContainer = lastTweensContainer;
+ }
+
+
+ /*********************************
+ Start: Tween Data Construction
+ *********************************/
+
+ } else if (action === 'start') {
+
+
+ /****************************
+ Start: Value Transferring
+ ****************************/
+
+ /* If this queue entry follows a previous Collide-initiated queue entry *and* if this entry was created
+ while the element was in the process of being animated by Collide, then this current call is safe to use
+ the end values from the prior call as its start values. Collide attempts to perform this value transfer
+ process whenever possible in order to avoid requerying the DOM. */
+ /* If values aren't transferred from a prior call and start values were not forcefed by the user (more on this below),
+ then the DOM is queried for the element's current values as a last resort. */
+ /* Note: Conversely, animation reversal (and looping) *always* perform inter-call value transfers; they never requery the DOM. */
+ var lastTweensContainer;
+
+ /* The per-element isAnimating flag is used to indicate whether it's safe (i.e. the data isn't stale)
+ to transfer over end values to use as start values. If it's set to true and there is a previous
+ Collide call to pull values from, do so. */
+ if (data(element).tweensContainer && data(element).isAnimating === true) {
+ lastTweensContainer = data(element).tweensContainer;
+ }
+
+
+ /********************************
+ Start: Tween Data Calculation
+ ********************************/
+
+ /* This function parses property data and defaults endValue, easing, and startValue as appropriate. */
+ /* Property map values can either take the form of 1) a single value representing the end value,
+ or 2) an array in the form of [ endValue, [, easing] [, startValue] ].
+ The optional third parameter is a forcefed startValue to be used instead of querying the DOM for
+ the element's current value. Read Velocity's docmentation to learn more about forcefeeding: VelocityJS.org/#forcefeeding */
+ function parsePropertyValue(valueData, skipResolvingEasing) {
+ var endValue = undefined,
+ easing = undefined,
+ startValue = undefined;
+
+ /* Handle the array format, which can be structured as one of three potential overloads:
+ A) [ endValue, easing, startValue ], B) [ endValue, easing ], or C) [ endValue, startValue ] */
+ if (Array.isArray(valueData)) {
+ /* endValue is always the first item in the array. Don't bother validating endValue's value now
+ since the ensuing property cycling logic does that. */
+ endValue = valueData[0];
+
+ /* Two-item array format: If the second item is a number, function, or hex string, treat it as a
+ start value since easings can only be non-hex strings or arrays. */
+ if ((!Array.isArray(valueData[1]) && /^[\d-]/.test(valueData[1])) || typeof valueData[1] === 'function' || CSS.RegEx.isHex.test(valueData[1])) {
+ startValue = valueData[1];
+
+ /* Two or three-item array: If the second item is a non-hex string or an array, treat it as an easing. */
+ } else if ((util.isString(valueData[1]) && !CSS.RegEx.isHex.test(valueData[1])) || Array.isArray(valueData[1])) {
+ easing = skipResolvingEasing ? valueData[1] : getEasing(valueData[1], opts.duration);
+
+ /* Don't bother validating startValue's value now since the ensuing property cycling logic inherently does that. */
+ if (valueData[2] !== undefined) {
+ startValue = valueData[2];
+ }
+ }
+
+ } else {
+ /* Handle the single-value format. */
+ endValue = valueData;
+ }
+
+ /* Default to the call's easing if a per-property easing type was not defined. */
+ if (!skipResolvingEasing) {
+ easing = easing || opts.easing;
+ }
+
+ /* If functions were passed in as values, pass the function the current element as its context,
+ plus the element's index and the element set's size as arguments. Then, assign the returned value. */
+ if (typeof endValue === 'function') {
+ endValue = endValue.call(element, elementsIndex, elementsLength);
+ }
+
+ if (typeof startValue === 'function') {
+ startValue = startValue.call(element, elementsIndex, elementsLength);
+ }
+
+ /* Allow startValue to be left as undefined to indicate to the ensuing code that its value was not forcefed. */
+ return [ endValue || 0, easing, startValue ];
+
+ } // END: parsePropertyValue()
+
+
+ /* Cycle through each property in the map, looking for shorthand color properties (e.g. 'color' as opposed to 'colorRed'). Inject the corresponding
+ colorRed, colorGreen, and colorBlue RGB component tweens into the propertiesMap (which Collide understands) and remove the shorthand property. */
+ for (var property in propertiesMap) {
+
+ /* Find shorthand color properties that have been passed a hex string. */
+ if (RegExp('^' + CSS.Lists.colors.join('$|^') + '$').test(property)) {
+ /* Parse the value data for each shorthand. */
+ var valueData = parsePropertyValue(propertiesMap[property], true),
+ endValue = valueData[0],
+ easing = valueData[1],
+ startValue = valueData[2];
+
+ if (CSS.RegEx.isHex.test(endValue)) {
+ /* Convert the hex strings into their RGB component arrays. */
+ var colorComponents = [ 'Red', 'Green', 'Blue' ],
+ endValueRGB = CSS.Values.hexToRgb(endValue),
+ startValueRGB = startValue ? CSS.Values.hexToRgb(startValue) : undefined;
+
+ /* Inject the RGB component tweens into propertiesMap. */
+ for (var i = 0; i < colorComponents.length; i++) {
+ var dataArray = [ endValueRGB[i] ];
+
+ if (easing) {
+ dataArray.push(easing);
+ }
+
+ if (startValueRGB !== undefined) {
+ dataArray.push(startValueRGB[i]);
+ }
+
+ propertiesMap[property + colorComponents[i]] = dataArray;
+ }
+
+ /* Remove the intermediary shorthand property entry now that we've processed it. */
+ delete propertiesMap[property];
+ }
+ }
+ }
+
+
+ /* Create a tween out of each property, and append its associated data to tweensContainer. */
+ for (var property in propertiesMap) {
+
+ /*********************************************
+ parsePropertyValue(), Start Value Sourcing
+ *********************************************/
+
+ /* Parse out endValue, easing, and startValue from the property's data. */
+ var valueData = parsePropertyValue(propertiesMap[property]),
+ endValue = valueData[0],
+ easing = valueData[1],
+ startValue = valueData[2];
+
+ /* Now that the original property name's format has been used for the parsePropertyValue() lookup above,
+ we force the property to its camelCase styling to normalize it for manipulation. */
+ property = CSS.Names.camelCase(property);
+
+ /* In case this property is a hook, there are circumstances where we will intend to work on the hook's root property and not the hooked subproperty. */
+ var rootProperty = CSS.Hooks.getRoot(property),
+ rootPropertyValue = false;
+
+ /* Other than for the dummy tween property, properties that are not supported by the browser (and do not have an associated normalization) will
+ inherently produce no style changes when set, so they are skipped in order to decrease animation tick overhead.
+ Property support is determined via prefixCheck(), which returns a false flag when no supported is detected. */
+ /* Note: Since SVG elements have some of their properties directly applied as HTML attributes,
+ there is no way to check for their explicit browser support, and so we skip skip this check for them. */
+ if (!data(element).isSVG && rootProperty !== 'tween' && CSS.Names.prefixCheck(rootProperty)[1] === false && CSS.Normalizations.registered[rootProperty] === undefined) {
+ //if (Collide.debug) console.log('Skipping [' + rootProperty + '] due to a lack of browser support.');
+ continue;
+ }
+
+ /* If the display option is being set to a non-'none' (e.g. 'block') and opacity (filter on IE<=8) is being
+ animated to an endValue of non-zero, the user's intention is to fade in from invisible, thus we forcefeed opacity
+ a startValue of 0 if its startValue hasn't already been sourced by value transferring or prior forcefeeding. */
+ if (((opts.display !== undefined && opts.display !== null && opts.display !== 'none') || (opts.visibility !== undefined && opts.visibility !== 'hidden')) && /opacity|filter/.test(property) && !startValue && endValue !== 0) {
+ startValue = 0;
+ }
+
+ /* If values have been transferred from the previous Collide call, extract the endValue and rootPropertyValue
+ for all of the current call's properties that were *also* animated in the previous call. */
+ /* Note: Value transferring can optionally be disabled by the user via the _cacheValues option. */
+ if (opts._cacheValues && lastTweensContainer && lastTweensContainer[property]) {
+ if (startValue === undefined) {
+ startValue = lastTweensContainer[property].endValue + lastTweensContainer[property].unitType;
+ }
+
+ /* The previous call's rootPropertyValue is extracted from the element's data cache since that's the
+ instance of rootPropertyValue that gets freshly updated by the tweening process, whereas the rootPropertyValue
+ attached to the incoming lastTweensContainer is equal to the root property's value prior to any tweening. */
+ rootPropertyValue = data(element).rootPropertyValueCache[rootProperty];
+
+ /* If values were not transferred from a previous Collide call, query the DOM as needed. */
+ } else {
+ /* Handle hooked properties. */
+ if (CSS.Hooks.registered[property]) {
+ if (startValue === undefined) {
+ rootPropertyValue = CSS.getPropertyValue(element, rootProperty); /* GET */
+ /* Note: The following getPropertyValue() call does not actually trigger a DOM query;
+ getPropertyValue() will extract the hook from rootPropertyValue. */
+ startValue = CSS.getPropertyValue(element, property, rootPropertyValue);
+
+ /* If startValue is already defined via forcefeeding, do not query the DOM for the root property's value;
+ just grab rootProperty's zero-value template from CSS.Hooks. This overwrites the element's actual
+ root property value (if one is set), but this is acceptable since the primary reason users forcefeed is
+ to avoid DOM queries, and thus we likewise avoid querying the DOM for the root property's value. */
+ } else {
+ /* Grab this hook's zero-value template, e.g. '0px 0px 0px black'. */
+ rootPropertyValue = CSS.Hooks.templates[rootProperty][1];
+ }
+
+ /* Handle non-hooked properties that haven't already been defined via forcefeeding. */
+ } else if (startValue === undefined) {
+ startValue = CSS.getPropertyValue(element, property); /* GET */
+ }
+ }
+
+
+ /**********************************************
+ parsePropertyValue(), Value Data Extraction
+ **********************************************/
+
+ var separatedValue,
+ endValueUnitType,
+ startValueUnitType,
+ operator = false;
+
+ /* Separates a property value into its numeric value and its unit util. */
+ function separateValue (property, value) {
+ var unitType,
+ numericValue;
+
+ numericValue = (value || '0')
+ .toString()
+ .toLowerCase()
+ /* Match the unit type at the end of the value. */
+ .replace(/[%A-z]+$/, function(match) {
+ /* Grab the unit util. */
+ unitType = match;
+
+ /* Strip the unit type off of value. */
+ return '';
+ });
+
+ /* If no unit type was supplied, assign one that is appropriate for this property (e.g. 'deg' for rotateZ or 'px' for width). */
+ if (!unitType) {
+ unitType = CSS.Values.getUnitType(property);
+ }
+
+ return [ numericValue, unitType ];
+ }
+
+ /* Separate startValue. */
+ separatedValue = separateValue(property, startValue);
+ startValue = separatedValue[0];
+ startValueUnitType = separatedValue[1];
+
+ /* Separate endValue, and extract a value operator (e.g. '+=', '-=') if one exists. */
+ separatedValue = separateValue(property, endValue);
+ endValue = separatedValue[0].replace(/^([+-\/*])=/, function(match, subMatch) {
+ operator = subMatch;
+
+ /* Strip the operator off of the value. */
+ return '';
+ });
+ endValueUnitType = separatedValue[1];
+
+ /* Parse float values from endValue and startValue. Default to 0 if NaN is returned. */
+ startValue = parseFloat(startValue) || 0;
+ endValue = parseFloat(endValue) || 0;
+
+
+ /***************************************
+ parsePropertyValue, Property-Specific Value Conversion
+ ***************************************/
+
+ /* Custom support for properties that don't actually accept the % unit type, but where pollyfilling is trivial and relatively foolproof. */
+ if (endValueUnitType === '%') {
+ /* A %-value fontSize/lineHeight is relative to the parent's fontSize (as opposed to the parent's dimensions),
+ which is identical to the em unit's behavior, so we piggyback off of that. */
+ if (/^(fontSize|lineHeight)$/.test(property)) {
+ /* Convert % into an em decimal value. */
+ endValue = endValue / 100;
+ endValueUnitType = 'em';
+
+ /* For scaleX and scaleY, convert the value into its decimal format and strip off the unit util. */
+ } else if (/^scale/.test(property)) {
+ endValue = endValue / 100;
+ endValueUnitType = '';
+
+ /* For RGB components, take the defined percentage of 255 and strip off the unit util. */
+ } else if (/(Red|Green|Blue)$/i.test(property)) {
+ endValue = (endValue / 100) * 255;
+ endValueUnitType = '';
+ }
+ }
+
+
+ /***************************
+ parsePropertyValue(), Unit Ratio Calculation
+ ***************************/
+
+ /* When queried, the browser returns (most) CSS property values in pixels. Therefore, if an endValue with a unit type of
+ %, em, or rem is animated toward, startValue must be converted from pixels into the same unit type as endValue in order
+ for value manipulation logic (increment/decrement) to proceed. Further, if the startValue was forcefed or transferred
+ from a previous call, startValue may also not be in pixels. Unit conversion logic therefore consists of two steps:
+ 1) Calculating the ratio of %/em/rem/vh/vw relative to pixels
+ 2) Converting startValue into the same unit of measurement as endValue based on these ratios. */
+ /* Unit conversion ratios are calculated by inserting a sibling node next to the target node, copying over its position property,
+ setting values with the target unit type then comparing the returned pixel value. */
+ /* Note: Even if only one of these unit types is being animated, all unit ratios are calculated at once since the overhead
+ of batching the SETs and GETs together upfront outweights the potential overhead
+ of layout thrashing caused by re-querying for uncalculated ratios for subsequently-processed properties. */
+ /* Todo: Shift this logic into the calls' first tick instance so that it's synced with RAF. */
+ function calculateUnitRatios() {
+
+ /**************************************************************
+ parsePropertyValue(), calculateUnitRatios(), Same Ratio Checks
+ **************************************************************/
+
+ /* The properties below are used to determine whether the element differs sufficiently from this call's
+ previously iterated element to also differ in its unit conversion ratios. If the properties match up with those
+ of the prior element, the prior element's conversion ratios are used. Like most optimizations in Collide,
+ this is done to minimize DOM querying. */
+ var sameRatioIndicators = {
+ myParent: element.parentNode || document.body, /* GET */
+ position: CSS.getPropertyValue(element, 'position'), /* GET */
+ fontSize: CSS.getPropertyValue(element, 'fontSize') /* GET */
+ };
+
+ /* Determine if the same % ratio can be used. % is based on the element's position value and its parent's width and height dimensions. */
+ var samePercentRatio = ((sameRatioIndicators.position === callUnitConversionData.lastPosition) && (sameRatioIndicators.myParent === callUnitConversionData.lastParent));
+
+ /* Determine if the same em ratio can be used. em is relative to the element's fontSize. */
+ var sameEmRatio = (sameRatioIndicators.fontSize === callUnitConversionData.lastFontSize);
+
+ /* Store these ratio indicators call-wide for the next element to compare against. */
+ callUnitConversionData.lastParent = sameRatioIndicators.myParent;
+ callUnitConversionData.lastPosition = sameRatioIndicators.position;
+ callUnitConversionData.lastFontSize = sameRatioIndicators.fontSize;
+
+
+ /**********************************************************************
+ parsePropertyValue(), calculateUnitRatios(), Element-Specific Units
+ **********************************************************************/
+
+ /* Note: IE8 rounds to the nearest pixel when returning CSS values, thus we perform conversions using a measurement
+ of 100 (instead of 1) to give our ratios a precision of at least 2 decimal values. */
+ var measurement = 100,
+ unitRatios = {};
+
+ if (!sameEmRatio || !samePercentRatio) {
+ var dummy = data(element).isSVG ? document.createElementNS('http://www.w3.org/2000/svg', 'rect') : document.createElement('div');
+
+ Collide.init(dummy);
+ sameRatioIndicators.myParent.appendChild(dummy);
+
+ /* To accurately and consistently calculate conversion ratios, the element's cascaded overflow and box-sizing are stripped.
+ Similarly, since width/height can be artificially constrained by their min-/max- equivalents, these are controlled for as well. */
+ /* Note: Overflow must be also be controlled for per-axis since the overflow property overwrites its per-axis values. */
+ var cssPropNames = [ 'overflow', 'overflowX', 'overflowY' ];
+ for (var x = 0; x < overflows.length; x++) {
+ Collide.CSS.setPropertyValue(dummy, cssPropNames[x], 'hidden');
+ }
+
+ Collide.CSS.setPropertyValue(dummy, 'position', sameRatioIndicators.position);
+ Collide.CSS.setPropertyValue(dummy, 'fontSize', sameRatioIndicators.fontSize);
+ Collide.CSS.setPropertyValue(dummy, 'boxSizing', 'content-box');
+
+ /* width and height act as our proxy properties for measuring the horizontal and vertical % ratios. */
+ cssPropNames = [ 'minWidth', 'maxWidth', 'width', 'minHeight', 'maxHeight', 'height' ];
+ for (var x = 0; x < overflows.length; x++) {
+ Collide.CSS.setPropertyValue(dummy, cssPropNames[x], measurement + '%');
+ }
+
+ /* paddingLeft arbitrarily acts as our proxy property for the em ratio. */
+ Collide.CSS.setPropertyValue(dummy, 'paddingLeft', measurement + 'em');
+
+ /* Divide the returned value by the measurement to get the ratio between 1% and 1px. Default to 1 since working with 0 can produce Infinite. */
+ unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth = (parseFloat(CSS.getPropertyValue(dummy, 'width', null, true)) || 1) / measurement; /* GET */
+ unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight = (parseFloat(CSS.getPropertyValue(dummy, 'height', null, true)) || 1) / measurement; /* GET */
+ unitRatios.emToPx = callUnitConversionData.lastEmToPx = (parseFloat(CSS.getPropertyValue(dummy, 'paddingLeft')) || 1) / measurement; /* GET */
+
+ sameRatioIndicators.myParent.removeChild(dummy);
+
+ } else {
+ unitRatios.emToPx = callUnitConversionData.lastEmToPx;
+ unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth;
+ unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight;
+ }
+
+
+ /**********************************************************************
+ parsePropertyValue(), calculateUnitRatios(), Element-Agnostic Units
+ ***********************************************************************/
+
+ /* Whereas % and em ratios are determined on a per-element basis, the rem unit only needs to be checked
+ once per call since it's exclusively dependant upon document.body's fontSize. If this is the first time
+ that calculateUnitRatios() is being run during this call, remToPx will still be set to its default value of null,
+ so we calculate it now. */
+ if (callUnitConversionData.remToPx === null) {
+ /* Default to browsers' default fontSize of 16px in the case of 0. */
+ callUnitConversionData.remToPx = parseFloat(CSS.getPropertyValue(document.body, 'fontSize')) || 16; /* GET */
+ }
+
+ /* Similarly, viewport units are %-relative to the window's inner dimensions. */
+ if (callUnitConversionData.vwToPx === null) {
+ callUnitConversionData.vwToPx = parseFloat(window.innerWidth) / 100; /* GET */
+ callUnitConversionData.vhToPx = parseFloat(window.innerHeight) / 100; /* GET */
+ }
+
+ unitRatios.remToPx = callUnitConversionData.remToPx;
+ unitRatios.vwToPx = callUnitConversionData.vwToPx;
+ unitRatios.vhToPx = callUnitConversionData.vhToPx;
+
+ //if (Collide.debug >= 1) console.log('Unit ratios: ' + JSON.stringify(unitRatios), element);
+
+ return unitRatios;
+
+ } // END: calculateUnitRatios()
+
+
+ /****************************************
+ parsePropertyValue(), Unit Conversion
+ *****************************************/
+
+ /* The * and / operators, which are not passed in with an associated unit, inherently use startValue's unit. Skip value and unit conversion. */
+ if (/[\/*]/.test(operator)) {
+ endValueUnitType = startValueUnitType;
+
+ /* If startValue and endValue differ in unit type, convert startValue into the same unit type as endValue so that if endValueUnitType
+ is a relative unit (%, em, rem), the values set during tweening will continue to be accurately relative even if the metrics they depend
+ on are dynamically changing during the course of the animation. Conversely, if we always normalized into px and used px for setting values, the px ratio
+ would become stale if the original unit being animated toward was relative and the underlying metrics change during the animation. */
+ /* Since 0 is 0 in any unit type, no conversion is necessary when startValue is 0 -- we just start at 0 with endValueUnitutil. */
+ } else if ((startValueUnitType !== endValueUnitType) && startValue !== 0) {
+ /* Unit conversion is also skipped when endValue is 0, but *startValueUnitType* must be used for tween values to remain accurate. */
+ /* Note: Skipping unit conversion here means that if endValueUnitType was originally a relative unit, the animation won't relatively
+ match the underlying metrics if they change, but this is acceptable since we're animating toward invisibility instead of toward visibility,
+ which remains past the point of the animation's completion. */
+ if (endValue === 0) {
+ endValueUnitType = startValueUnitType;
+
+ } else {
+ /* By this point, we cannot avoid unit conversion (it's undesirable since it causes layout thrashing).
+ If we haven't already, we trigger calculateUnitRatios(), which runs once per element per call. */
+ elementUnitConversionData = elementUnitConversionData || calculateUnitRatios();
+
+ /* The following RegEx matches CSS properties that have their % values measured relative to the x-axis. */
+ /* Note: W3C spec mandates that all of margin and padding's properties (even top and bottom) are %-relative to the *width* of the parent element. */
+ var axis = (/margin|padding|left|right|width|text|word|letter/i.test(property) || /X$/.test(property) || property === 'x') ? 'x' : 'y';
+
+ /* In order to avoid generating n^2 bespoke conversion functions, unit conversion is a two-step process:
+ 1) Convert startValue into pixels. 2) Convert this new pixel value into endValue's unit util. */
+ switch (startValueUnitType) {
+ case '%':
+ /* Note: translateX and translateY are the only properties that are %-relative to an element's own dimensions -- not its parent's dimensions.
+ Collide does not include a special conversion process to account for this behavior. Therefore, animating translateX/Y from a % value
+ to a non-% value will produce an incorrect start value. Fortunately, this sort of cross-unit conversion is rarely done by users in practice. */
+ startValue *= (axis === 'x' ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight);
+ break;
+
+ case 'px':
+ /* px acts as our midpoint in the unit conversion process; do nothing. */
+ break;
+
+ default:
+ startValue *= elementUnitConversionData[startValueUnitType + 'ToPx'];
+ }
+
+ /* Invert the px ratios to convert into to the target unit. */
+ switch (endValueUnitType) {
+ case '%':
+ startValue *= 1 / (axis === 'x' ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight);
+ break;
+
+ case 'px':
+ /* startValue is already in px, do nothing; we're done. */
+ break;
+
+ default:
+ startValue *= 1 / elementUnitConversionData[endValueUnitType + 'ToPx'];
+ }
+ }
+ }
+
+
+ /****************************************
+ parsePropertyValue(), Relative Values
+ ****************************************/
+
+ /* Operator logic must be performed last since it requires unit-normalized start and end values. */
+ /* Note: Relative *percent values* do not behave how most people think; while one would expect '+=50%'
+ to increase the property 1.5x its current value, it in fact increases the percent units in absolute terms:
+ 50 points is added on top of the current % value. */
+ switch (operator) {
+ case '+':
+ endValue = startValue + endValue;
+ break;
+
+ case '-':
+ endValue = startValue - endValue;
+ break;
+
+ case '*':
+ endValue = startValue * endValue;
+ break;
+
+ case '/':
+ endValue = startValue / endValue;
+ break;
+ }
+
+
+ /*********************************************
+ parsePropertyValue(), tweensContainer Push
+ *********************************************/
+
+ /* Construct the per-property tween object, and push it to the element's tweensContainer. */
+ tweensContainer[property] = {
+ rootPropertyValue: rootPropertyValue,
+ startValue: startValue,
+ currentValue: startValue,
+ endValue: endValue,
+ unitType: endValueUnitType,
+ easing: easing
+ };
+
+ //if (Collide.debug) console.log('tweensContainer (' + property + '): ' + JSON.stringify(tweensContainer[property]), element);
+ }
+
+ /* Along with its property data, store a reference to the element itself onto tweensContainer. */
+ tweensContainer.element = element;
+
+ } // END: parsePropertyValue()
+
+
+ /*****************
+ Call Push
+ *****************/
+
+ /* Note: tweensContainer can be empty if all of the properties in this call's property map were skipped due to not
+ being supported by the browser. The element property is used for checking that the tweensContainer has been appended to. */
+ if (tweensContainer.element) {
+
+ /* The call array houses the tweensContainers for each element being animated in the current call. */
+ call.push(tweensContainer);
+
+ /* Store the tweensContainer and options if we're working on the default effects queue, so that they can be used by the reverse command. */
+ if (opts.queue === '') {
+ data(element).tweensContainer = tweensContainer;
+ data(element).opts = opts;
+ }
+
+ /* Switch on the element's animating flag. */
+ data(element).isAnimating = true;
+
+ /* Once the final element in this call's element set has been processed, push the call array onto
+ Collide.State.calls for the animation tick to immediately begin processing. */
+ if (elementsIndex === elementsLength - 1) {
+ /* Add the current call plus its associated metadata (the element set and the call's options) onto the global call container.
+ Anything on this call container is subjected to tick() processing. */
+ Collide.State.calls.push([ call, elements, opts, null, promiseData.resolver ]);
+
+ /* If the animation tick isn't running, start it. (Collide shuts it off when there are no active calls to process.) */
+ if (Collide.State.isTicking === false) {
+ Collide.State.isTicking = true;
+
+ /* Start the tick loop. */
+ tick();
+ }
+
+ } else {
+ elementsIndex++;
+ }
+ }
+
+ } // END: for (var elementsIndex = 0; elementsIndex < transition.elements.length; x++) {
+
+
+ /* When the queue option is set to false, the call skips the element's queue and fires immediately. */
+ if (opts.queue === false) {
+ /* Since this buildQueue call doesn't respect the element's existing queue (which is where a delay option would have been appended),
+ we manually inject the delay property here with an explicit setTimeout. */
+ if (opts.delay) {
+ setTimeout(buildQueue, opts.delay);
+ } else {
+ buildQueue();
+ }
+
+ /* Otherwise, the call undergoes element queueing as normal. */
+ } else {
+ Collide.queue(element, opts.queue, function(next, clearQueue) {
+ /* If the clearQueue flag was passed in by the stop command, resolve this call's promise. (Promises can only be resolved once,
+ so it's fine if this is repeatedly triggered for each element in the associated call.) */
+ if (clearQueue === true) {
+ if (promiseData.promise) {
+ promiseData.resolver(elements);
+ }
+
+ /* Do not continue with animation queueing. */
+ return true;
+ }
+
+ /* This flag indicates to the upcoming completeCall() function that this queue entry was initiated by Collide.
+ See completeCall() for further details. */
+ Collide.CollideQueueEntryFlag = true;
+
+ buildQueue(next);
+ });
+ }
+
+
+ /*********************
+ Auto-Dequeuing
+ *********************/
+
+ /* To fire the first non-custom-queue entry on an element, the element
+ must be dequeued if its queue stack consists *solely* of the current call. (This can be determined by checking
+ for the 'inprogress' item that jQuery prepends to active queue stack arrays.) Regardless, whenever the element's
+ queue is further appended with additional items -- including $.delay()'s or even $.animate() calls, the queue's
+ first entry is automatically fired. This behavior contrasts that of custom queues, which never auto-fire. */
+ /* Note: When an element set is being subjected to a non-parallel Collide call, the animation will not begin until
+ each one of the elements in the set has reached the end of its individually pre-existing queue chain. */
+ /* Note: Unfortunately, most people don't fully grasp jQuery's powerful, yet quirky, Collide.queue() function.
+ Lean more here: http://stackoverflow.com/questions/1058158/can-somebody-explain-jquery-queue-to-me */
+ if ((opts.queue === '' || opts.queue === 'fx') && Collide.queue(element)[0] !== 'inprogress') {
+ Collide.dequeue(element);
+ }
+
+ }
+
+}
diff --git a/src/collide/transition.js b/src/collide/transition.js
new file mode 100644
index 0000000000..084d841581
--- /dev/null
+++ b/src/collide/transition.js
@@ -0,0 +1,63 @@
+import {initTransition} from 'ionic2/collide/init-transition'
+import {processTransition} from 'ionic2/collide/process-transition'
+
+
+
+export class Transition {
+ constructor(ele) {
+ console.log('Transition', ele)
+
+ if (!ele || ele.length === 0) return
+
+ this.elements = !ele.length ? [ele] : ele
+ ele = null
+
+ // Animations that happen to this element
+ this.animations = []
+
+ // Sub transitions that happen to sub elements
+ this.transitions = []
+
+ this.options = {}
+ this.propertiesMap = {}
+
+ this.isRunning = false
+ }
+
+ start() {
+ console.log('start')
+ initTransition('start', this.elements, this.options, this.propertiesMap)
+ }
+
+ stop() {
+ console.log('stop')
+ }
+
+ properties(val) {
+ this.propertiesMap = val || {}
+ }
+
+ property(key, val) {
+ this.propertiesMap[key] = val
+ }
+
+ removeProperty(key) {
+ delete this.propertiesMap[key]
+ }
+
+ duration(val) {
+ this.options.duration = val
+ }
+
+ easing(val) {
+ this.options.easing = val
+ }
+
+}
+
+
+export class IOSTransition extends Transition {
+ constructor(ele) {
+ super(ele)
+ }
+}
diff --git a/src/components/app/test/testing/layout.html b/src/components/app/test/testing/layout.html
deleted file mode 100644
index cf1dc8414d..0000000000
--- a/src/components/app/test/testing/layout.html
+++ /dev/null
@@ -1,160 +0,0 @@
-
-
-
-