This repository has been archived by the owner on Sep 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
fabSpeedDial.js
253 lines (217 loc) · 8.62 KB
/
fabSpeedDial.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
(function() {
'use strict';
/**
* The duration of the CSS animation in milliseconds.
*
* @type {number}
*/
var cssAnimationDuration = 300;
/**
* @ngdoc module
* @name material.components.fabSpeedDial
*/
angular
// Declare our module
.module('material.components.fabSpeedDial', [
'material.core',
'material.components.fabShared',
'material.components.fabActions'
])
// Register our directive
.directive('mdFabSpeedDial', MdFabSpeedDialDirective)
// Register our custom animations
.animation('.md-fling', MdFabSpeedDialFlingAnimation)
.animation('.md-scale', MdFabSpeedDialScaleAnimation)
// Register a service for each animation so that we can easily inject them into unit tests
.service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation)
.service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation);
/**
* @ngdoc directive
* @name mdFabSpeedDial
* @module material.components.fabSpeedDial
*
* @restrict E
*
* @description
* The `<md-fab-speed-dial>` directive is used to present a series of popup elements (usually
* `<md-button>`s) for quick access to common actions.
*
* There are currently two animations available by applying one of the following classes to
* the component:
*
* - `md-fling` - The speed dial items appear from underneath the trigger and move into their
* appropriate positions.
* - `md-scale` - The speed dial items appear in their proper places by scaling from 0% to 100%.
*
* You may also easily position the trigger by applying one one of the following classes to the
* `<md-fab-speed-dial>` element:
* - `md-fab-top-left`
* - `md-fab-top-right`
* - `md-fab-bottom-left`
* - `md-fab-bottom-right`
*
* These CSS classes use `position: absolute`, so you need to ensure that the container element
* also uses `position: absolute` or `position: relative` in order for them to work.
*
* Additionally, you may use the standard `ng-mouseenter` and `ng-mouseleave` directives to
* open or close the speed dial. However, if you wish to allow users to hover over the empty
* space where the actions will appear, you must also add the `md-hover-full` class to the speed
* dial element. Without this, the hover effect will only occur on top of the trigger.
*
* See the demos for more information.
*
* ## Troubleshooting
*
* If your speed dial shows the closing animation upon launch, you may need to use `ng-cloak` on
* the parent container to ensure that it is only visible once ready. We have plans to remove this
* necessity in the future.
*
* @usage
* <hljs lang="html">
* <md-fab-speed-dial md-direction="up" class="md-fling">
* <md-fab-trigger>
* <md-button aria-label="Add..."><md-icon md-svg-src="/img/icons/plus.svg"></md-icon></md-button>
* </md-fab-trigger>
*
* <md-fab-actions>
* <md-button aria-label="Add User">
* <md-icon md-svg-src="/img/icons/user.svg"></md-icon>
* </md-button>
*
* <md-button aria-label="Add Group">
* <md-icon md-svg-src="/img/icons/group.svg"></md-icon>
* </md-button>
* </md-fab-actions>
* </md-fab-speed-dial>
* </hljs>
*
* @param {string} md-direction From which direction you would like the speed dial to appear
* relative to the trigger element.
* @param {expression=} md-open Programmatically control whether or not the speed-dial is visible.
*/
function MdFabSpeedDialDirective() {
return {
restrict: 'E',
scope: {
direction: '@?mdDirection',
isOpen: '=?mdOpen'
},
bindToController: true,
controller: 'MdFabController',
controllerAs: 'ctrl',
link: FabSpeedDialLink
};
function FabSpeedDialLink(scope, element) {
// Prepend an element to hold our CSS variables so we can use them in the animations below
element.prepend('<div class="_md-css-variables"></div>');
}
}
function MdFabSpeedDialFlingAnimation($timeout) {
function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
function runAnimation(element) {
// Don't run if we are still waiting and we are not ready
if (element.hasClass('md-animations-waiting') && !element.hasClass('_md-animations-ready')) {
return;
}
var el = element[0];
var ctrl = element.controller('mdFabSpeedDial');
var items = el.querySelectorAll('.md-fab-action-item');
// Grab our trigger element
var triggerElement = el.querySelector('md-fab-trigger');
// Grab our element which stores CSS variables
var variablesElement = el.querySelector('._md-css-variables');
// Setup JS variables based on our CSS variables
var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
// Always reset the items to their natural position/state
angular.forEach(items, function(item, index) {
var styles = item.style;
styles.transform = styles.webkitTransform = '';
styles.transitionDelay = '';
styles.opacity = ctrl.isOpen ? 1 : 0;
// Make the items closest to the trigger have the highest z-index
styles.zIndex = (items.length - index) + startZIndex;
});
// Set the trigger to be above all of the actions so they disappear behind it.
triggerElement.style.zIndex = startZIndex + items.length + 1;
// If the control is closed, hide the items behind the trigger
if (!ctrl.isOpen) {
angular.forEach(items, function(item, index) {
var newPosition, axis;
var styles = item.style;
// Make sure to account for differences in the dimensions of the trigger verses the items
// so that we can properly center everything; this helps hide the item's shadows behind
// the trigger.
var triggerItemHeightOffset = (triggerElement.clientHeight - item.clientHeight) / 2;
var triggerItemWidthOffset = (triggerElement.clientWidth - item.clientWidth) / 2;
switch (ctrl.direction) {
case 'up':
newPosition = (item.scrollHeight * (index + 1) + triggerItemHeightOffset);
axis = 'Y';
break;
case 'down':
newPosition = -(item.scrollHeight * (index + 1) + triggerItemHeightOffset);
axis = 'Y';
break;
case 'left':
newPosition = (item.scrollWidth * (index + 1) + triggerItemWidthOffset);
axis = 'X';
break;
case 'right':
newPosition = -(item.scrollWidth * (index + 1) + triggerItemWidthOffset);
axis = 'X';
break;
}
var newTranslate = 'translate' + axis + '(' + newPosition + 'px)';
styles.transform = styles.webkitTransform = newTranslate;
});
}
}
return {
addClass: function(element, className, done) {
if (element.hasClass('md-fling')) {
runAnimation(element);
delayDone(done);
} else {
done();
}
},
removeClass: function(element, className, done) {
runAnimation(element);
delayDone(done);
}
};
}
function MdFabSpeedDialScaleAnimation($timeout) {
function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
var delay = 65;
function runAnimation(element) {
var el = element[0];
var ctrl = element.controller('mdFabSpeedDial');
var items = el.querySelectorAll('.md-fab-action-item');
// Grab our element which stores CSS variables
var variablesElement = el.querySelector('._md-css-variables');
// Setup JS variables based on our CSS variables
var startZIndex = parseInt(window.getComputedStyle(variablesElement).zIndex);
// Always reset the items to their natural position/state
angular.forEach(items, function(item, index) {
var styles = item.style,
offsetDelay = index * delay;
styles.opacity = ctrl.isOpen ? 1 : 0;
styles.transform = styles.webkitTransform = ctrl.isOpen ? 'scale(1)' : 'scale(0)';
styles.transitionDelay = (ctrl.isOpen ? offsetDelay : (items.length - offsetDelay)) + 'ms';
// Make the items closest to the trigger have the highest z-index
styles.zIndex = (items.length - index) + startZIndex;
});
}
return {
addClass: function(element, className, done) {
runAnimation(element);
delayDone(done);
},
removeClass: function(element, className, done) {
runAnimation(element);
delayDone(done);
}
};
}
})();