Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 4770f64

Browse files
committed
fix(speedDial): Don't intercept spaces and fix animations.
* The speed dial watched the document for spacebar presses and intercepted them by preventing the default. There was really no reason for this, so the code has been removed. Additionally, all keypress events are now bound to the element itself instead of document. * Fix the close animations to properly fire on Safari/iOS. * Fix some issues after recent gesture updates. Fixes #5085. Fixes #4750. Fixes #5065.
1 parent bfc40ff commit 4770f64

File tree

4 files changed

+66
-90
lines changed

4 files changed

+66
-90
lines changed

src/components/fabSpeedDial/fabController.js

+34-32
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@
4444

4545
function setupListeners() {
4646
var eventTypes = [
47-
'$md.pressdown',
48-
49-
'click', // Fired via keyboard ENTER
50-
51-
'focusin', 'focusout'
47+
'click', 'focusin', 'focusout'
5248
];
5349

5450
// Add our listeners
@@ -68,34 +64,25 @@
6864
});
6965
}
7066

71-
var recentEvent;
67+
var closeTimeout;
7268
function parseEvents(event) {
73-
// If we've had a recent press/click event, or material is sending us an additional event,
74-
// ignore it
75-
if (recentEvent && (isClick(recentEvent) || recentEvent.$material)) {
76-
return;
77-
}
78-
79-
// Otherwise, handle our events
80-
if (isClick(event)) {
69+
// If the event is a click, just handle it
70+
if (event.type == 'click') {
8171
handleItemClick(event);
82-
83-
// Store our recent click event
84-
recentEvent = event;
85-
} else if (event.type == 'focusin') {
86-
vm.open();
87-
} else if (event.type == 'focusout') {
88-
vm.close();
8972
}
9073

91-
// Clear the recent event after all others have fired so we stop ignoring
92-
$timeout(function() {
93-
recentEvent = null;
94-
}, 100, false);
95-
}
74+
// If we focusout, set a timeout to close the element
75+
if (event.type == 'focusout' && !closeTimeout) {
76+
closeTimeout = $timeout(function() {
77+
vm.close();
78+
}, 100, false);
79+
}
9680

97-
function isClick(event) {
98-
return event.type == '$md.pressdown' || event.type == 'click';
81+
// If we see a focusin and there is a timeout about to run, cancel it so we stay open
82+
if (event.type == 'focusin' && closeTimeout) {
83+
$timeout.cancel(closeTimeout);
84+
closeTimeout = null;
85+
}
9986
}
10087

10188
function resetActionIndex() {
@@ -154,19 +141,34 @@
154141
}
155142

156143
function enableKeyboard() {
157-
angular.element(document).on('keydown', keyPressed);
144+
$element.on('keydown', keyPressed);
145+
angular.element(document).on('click', checkForOutsideClick);
146+
angular.element(document).on('touchend', checkForOutsideClick);
158147

159-
// TODO: On desktop, we should be able to reset the indexes so you cannot tab through
148+
// TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
149+
// this breaks accessibility, especially on mobile, since you have no arrow keys to press
160150
//resetActionTabIndexes();
161151
}
162152

163153
function disableKeyboard() {
164-
angular.element(document).off('keydown', keyPressed);
154+
$element.off('keydown', keyPressed);
155+
angular.element(document).off('click', checkForOutsideClick);
156+
angular.element(document).off('touchend', checkForOutsideClick);
157+
}
158+
159+
function checkForOutsideClick(event) {
160+
if (event.target) {
161+
var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger');
162+
var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions');
163+
164+
if (!closestTrigger && !closestActions) {
165+
vm.close();
166+
}
167+
}
165168
}
166169

167170
function keyPressed(event) {
168171
switch (event.which) {
169-
case $mdConstant.KEY_CODE.SPACE: event.preventDefault(); return false;
170172
case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false;
171173
case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
172174
case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;

src/components/fabSpeedDial/fabSpeedDial.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
(function() {
22
'use strict';
33

4+
/**
5+
* The duration of the CSS animation in milliseconds.
6+
*
7+
* @type {number}
8+
*/
9+
var cssAnimationDuration = 300;
10+
411
/**
512
* @ngdoc module
613
* @name material.components.fabSpeedDial
@@ -103,7 +110,9 @@
103110
}
104111
}
105112

106-
function MdFabSpeedDialFlingAnimation() {
113+
function MdFabSpeedDialFlingAnimation($timeout) {
114+
function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
115+
107116
function runAnimation(element) {
108117
var el = element[0];
109118
var ctrl = element.controller('mdFabSpeedDial');
@@ -169,17 +178,19 @@
169178
addClass: function(element, className, done) {
170179
if (element.hasClass('md-fling')) {
171180
runAnimation(element);
172-
done();
173181
}
182+
delayDone(done);
174183
},
175184
removeClass: function(element, className, done) {
176185
runAnimation(element);
177-
done();
186+
delayDone(done);
178187
}
179188
}
180189
}
181190

182-
function MdFabSpeedDialScaleAnimation() {
191+
function MdFabSpeedDialScaleAnimation($timeout) {
192+
function delayDone(done) { $timeout(done, cssAnimationDuration, false); }
193+
183194
var delay = 65;
184195

185196
function runAnimation(element) {
@@ -210,12 +221,12 @@
210221
return {
211222
addClass: function(element, className, done) {
212223
runAnimation(element);
213-
done();
224+
delayDone(done);
214225
},
215226

216227
removeClass: function(element, className, done) {
217228
runAnimation(element);
218-
done();
229+
delayDone(done);
219230
}
220231
}
221232
}

src/components/fabSpeedDial/fabSpeedDial.scss

+9-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ md-fab-speed-dial {
3131

3232
&.md-is-open {
3333
.md-fab-action-item {
34-
visibility: visible;
3534
align-items: center;
3635
}
3736
}
@@ -43,7 +42,6 @@ md-fab-speed-dial {
4342
height: auto;
4443

4544
.md-fab-action-item {
46-
visibility: hidden;
4745
transition: $swift-ease-in;
4846
}
4947
}
@@ -108,6 +106,15 @@ md-fab-speed-dial {
108106
}
109107
}
110108

109+
/*
110+
* Hide some graphics glitches if switching animation types
111+
*/
112+
&.md-fling-remove, &.md-scale-remove {
113+
.md-fab-action-item > * {
114+
visibility: hidden;
115+
}
116+
}
117+
111118
/*
112119
* Handle the animations
113120
*/

src/components/fabSpeedDial/fabSpeedDial.spec.js

+6-50
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('<md-fab-speed-dial> directive', function() {
5151

5252
it('toggles the menu when the trigger clicked', inject(function() {
5353
build(
54-
'<md-fab-speed-dial>' +
54+
'<md-fab-speed-dial>' +
5555
' <md-fab-trigger>' +
5656
' <md-button></md-button>' +
5757
' </md-fab-trigger>' +
@@ -107,55 +107,7 @@ describe('<md-fab-speed-dial> directive', function() {
107107
expect(controller.isOpen).toBe(false);
108108
}));
109109

110-
it('opens the menu when the trigger is focused', inject(function() {
111-
build(
112-
'<md-fab-speed-dial>' +
113-
' <md-fab-trigger>' +
114-
' <md-button></md-button>' +
115-
' </md-fab-trigger>' +
116-
'</md-fab-speed-dial>'
117-
);
118-
119-
var focusEvent = {
120-
type: 'focusin',
121-
target: element.find('md-fab-trigger').find('md-button')
122-
};
123-
124-
element.triggerHandler(focusEvent);
125-
pageScope.$digest();
126-
expect(controller.isOpen).toBe(true);
127-
}));
128-
129-
it('closes the menu when the trigger is blurred', inject(function() {
130-
build(
131-
'<md-fab-speed-dial>' +
132-
' <md-fab-trigger>' +
133-
' <md-button></md-button>' +
134-
' </md-fab-trigger>' +
135-
'</md-fab-speed-dial>'
136-
);
137-
138-
var focusInEvent = {
139-
type: 'focusin',
140-
target: element.find('md-fab-trigger').find('md-button')
141-
};
142-
143-
var focusOutEvent = {
144-
type: 'focusout',
145-
target: element.find('md-fab-trigger').find('md-button')
146-
};
147-
148-
element.triggerHandler(focusInEvent);
149-
pageScope.$digest();
150-
expect(controller.isOpen).toBe(true);
151-
152-
element.triggerHandler(focusOutEvent);
153-
pageScope.$digest();
154-
expect(controller.isOpen).toBe(false);
155-
}));
156-
157-
158-
it('properly finishes the fling animation', inject(function(mdFabSpeedDialFlingAnimation) {
110+
it('properly finishes the fling animation', inject(function(mdFabSpeedDialFlingAnimation, $timeout) {
159111
build(
160112
'<md-fab-speed-dial md-open="isOpen" class="md-fling">' +
161113
' <md-fab-trigger><button></button></md-fab-trigger>' +
@@ -167,9 +119,11 @@ describe('<md-fab-speed-dial> directive', function() {
167119
var removeDone = jasmine.createSpy('removeDone');
168120

169121
mdFabSpeedDialFlingAnimation.addClass(element, 'md-is-open', addDone);
122+
$timeout.flush();
170123
expect(addDone).toHaveBeenCalled();
171124

172125
mdFabSpeedDialFlingAnimation.removeClass(element, 'md-is-open', removeDone);
126+
$timeout.flush();
173127
expect(removeDone).toHaveBeenCalled();
174128
}));
175129

@@ -185,9 +139,11 @@ describe('<md-fab-speed-dial> directive', function() {
185139
var removeDone = jasmine.createSpy('removeDone');
186140

187141
mdFabSpeedDialScaleAnimation.addClass(element, 'md-is-open', addDone);
142+
$timeout.flush();
188143
expect(addDone).toHaveBeenCalled();
189144

190145
mdFabSpeedDialScaleAnimation.removeClass(element, 'md-is-open', removeDone);
146+
$timeout.flush();
191147
expect(removeDone).toHaveBeenCalled();
192148
}));
193149

0 commit comments

Comments
 (0)