forked from angular/angular.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanimate.js
371 lines (333 loc) · 14 KB
/
animate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
'use strict';
var $animateMinErr = minErr('$animate');
/**
* @ngdoc provider
* @name $animateProvider
*
* @description
* Default implementation of $animate that doesn't perform any animations, instead just
* synchronously performs DOM
* updates and calls done() callbacks.
*
* In order to enable animations the ngAnimate module has to be loaded.
*
* To see the functional implementation check out src/ngAnimate/animate.js
*/
var $AnimateProvider = ['$provide', function($provide) {
this.$$selectors = {};
/**
* @ngdoc method
* @name $animateProvider#register
*
* @description
* Registers a new injectable animation factory function. The factory function produces the
* animation object which contains callback functions for each event that is expected to be
* animated.
*
* * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction`
* must be called once the element animation is complete. If a function is returned then the
* animation service will use this function to cancel the animation whenever a cancel event is
* triggered.
*
*
* ```js
* return {
* eventFn : function(element, done) {
* //code to run the animation
* //once complete, then run done()
* return function cancellationFunction() {
* //code to cancel the animation
* }
* }
* }
* ```
*
* @param {string} name The name of the animation.
* @param {Function} factory The factory function that will be executed to return the animation
* object.
*/
this.register = function(name, factory) {
var key = name + '-animation';
if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel',
"Expecting class selector starting with '.' got '{0}'.", name);
this.$$selectors[name.substr(1)] = key;
$provide.factory(key, factory);
};
/**
* @ngdoc method
* @name $animateProvider#classNameFilter
*
* @description
* Sets and/or returns the CSS class regular expression that is checked when performing
* an animation. Upon bootstrap the classNameFilter value is not set at all and will
* therefore enable $animate to attempt to perform an animation on any element.
* When setting the classNameFilter value, animations will only be performed on elements
* that successfully match the filter expression. This in turn can boost performance
* for low-powered devices as well as applications containing a lot of structural operations.
* @param {RegExp=} expression The className expression which will be checked against all animations
* @return {RegExp} The current CSS className expression value. If null then there is no expression value
*/
this.classNameFilter = function(expression) {
if (arguments.length === 1) {
this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
}
return this.$$classNameFilter;
};
this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
var currentDefer;
function runAnimationPostDigest(fn) {
var cancelFn, defer = $$q.defer();
defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
cancelFn && cancelFn();
};
$rootScope.$$postDigest(function ngAnimatePostDigest() {
cancelFn = fn(function ngAnimateNotifyComplete() {
defer.resolve();
});
});
return defer.promise;
}
function resolveElementClasses(element, classes) {
var toAdd = [], toRemove = [];
var hasClasses = createMap();
forEach((element.attr('class') || '').split(/\s+/), function(className) {
hasClasses[className] = true;
});
forEach(classes, function(status, className) {
var hasClass = hasClasses[className];
// If the most recent class manipulation (via $animate) was to remove the class, and the
// element currently has the class, the class is scheduled for removal. Otherwise, if
// the most recent class manipulation (via $animate) was to add the class, and the
// element does not currently have the class, the class is scheduled to be added.
if (status === false && hasClass) {
toRemove.push(className);
} else if (status === true && !hasClass) {
toAdd.push(className);
}
});
return (toAdd.length + toRemove.length) > 0 &&
[toAdd.length ? toAdd : null, toRemove.length ? toRemove : null];
}
function cachedClassManipulation(cache, classes, op) {
for (var i=0, ii = classes.length; i < ii; ++i) {
var className = classes[i];
cache[className] = op;
}
}
function asyncPromise() {
// only serve one instance of a promise in order to save CPU cycles
if (!currentDefer) {
currentDefer = $$q.defer();
$$asyncCallback(function() {
currentDefer.resolve();
currentDefer = null;
});
}
return currentDefer.promise;
}
function applyStyles(element, options) {
if (angular.isObject(options)) {
var styles = extend(options.from || {}, options.to || {});
element.css(styles);
}
}
/**
*
* @ngdoc service
* @name $animate
* @description The $animate service provides rudimentary DOM manipulation functions to
* insert, remove and move elements within the DOM, as well as adding and removing classes.
* This service is the core service used by the ngAnimate $animator service which provides
* high-level animation hooks for CSS and JavaScript.
*
* $animate is available in the AngularJS core, however, the ngAnimate module must be included
* to enable full out animation support. Otherwise, $animate will only perform simple DOM
* manipulation operations.
*
* To learn more about enabling animation support, click here to visit the {@link ngAnimate
* ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service
* page}.
*/
return {
animate: function(element, from, to) {
applyStyles(element, { from: from, to: to });
return asyncPromise();
},
/**
*
* @ngdoc method
* @name $animate#enter
* @kind function
* @description Inserts the element into the DOM either after the `after` element or
* as the first child within the `parent` element. When the function is called a promise
* is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be inserted into the DOM
* @param {DOMElement} parent the parent element which will append the element as
* a child (if the after element is not present)
* @param {DOMElement} after the sibling element which will append the element
* after itself
* @param {object=} options an optional collection of styles that will be applied to the element.
* @return {Promise} the animation callback promise
*/
enter: function(element, parent, after, options) {
applyStyles(element, options);
after ? after.after(element)
: parent.prepend(element);
return asyncPromise();
},
/**
*
* @ngdoc method
* @name $animate#leave
* @kind function
* @description Removes the element from the DOM. When the function is called a promise
* is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be removed from the DOM
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
leave: function(element, options) {
element.remove();
return asyncPromise();
},
/**
*
* @ngdoc method
* @name $animate#move
* @kind function
* @description Moves the position of the provided element within the DOM to be placed
* either after the `after` element or inside of the `parent` element. When the function
* is called a promise is returned that will be resolved at a later time.
*
* @param {DOMElement} element the element which will be moved around within the
* DOM
* @param {DOMElement} parent the parent element where the element will be
* inserted into (if the after element is not present)
* @param {DOMElement} after the sibling element where the element will be
* positioned next to
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
move: function(element, parent, after, options) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
return this.enter(element, parent, after, options);
},
/**
*
* @ngdoc method
* @name $animate#addClass
* @kind function
* @description Adds the provided className CSS class value to the provided element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* added to it
* @param {string} className the CSS class which will be added to the element
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
addClass: function(element, className, options) {
return this.setClass(element, className, [], options);
},
$$addClassImmediately: function(element, className, options) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function(element) {
jqLiteAddClass(element, className);
});
applyStyles(element, options);
return asyncPromise();
},
/**
*
* @ngdoc method
* @name $animate#removeClass
* @kind function
* @description Removes the provided className CSS class value from the provided element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* removed from it
* @param {string} className the CSS class which will be removed from the element
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
removeClass: function(element, className, options) {
return this.setClass(element, [], className, options);
},
$$removeClassImmediately: function(element, className, options) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function(element) {
jqLiteRemoveClass(element, className);
});
applyStyles(element, options);
return asyncPromise();
},
/**
*
* @ngdoc method
* @name $animate#setClass
* @kind function
* @description Adds and/or removes the given CSS classes to and from the element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
setClass: function(element, add, remove, options) {
var self = this;
var STORAGE_KEY = '$$animateClasses';
var createdCache = false;
element = jqLite(element);
var cache = element.data(STORAGE_KEY);
if (!cache) {
cache = {
classes: {},
options: options
};
createdCache = true;
} else if (options && cache.options) {
cache.options = angular.extend(cache.options || {}, options);
}
var classes = cache.classes;
add = isArray(add) ? add : add.split(' ');
remove = isArray(remove) ? remove : remove.split(' ');
cachedClassManipulation(classes, add, true);
cachedClassManipulation(classes, remove, false);
if (createdCache) {
cache.promise = runAnimationPostDigest(function(done) {
var cache = element.data(STORAGE_KEY);
element.removeData(STORAGE_KEY);
// in the event that the element is removed before postDigest
// is run then the cache will be undefined and there will be
// no need anymore to add or remove and of the element classes
if (cache) {
var classes = resolveElementClasses(element, cache.classes);
if (classes) {
self.$$setClassImmediately(element, classes[0], classes[1], cache.options);
}
}
done();
});
element.data(STORAGE_KEY, cache);
}
return cache.promise;
},
$$setClassImmediately: function(element, add, remove, options) {
add && this.$$addClassImmediately(element, add);
remove && this.$$removeClassImmediately(element, remove);
applyStyles(element, options);
return asyncPromise();
},
enabled: noop,
cancel: noop
};
}];
}];