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

Commit e7dfcc1

Browse files
committed
fix(fab-speed-dial): opens when trigger button is disabled
- when the trigger is clicked, verify that the trigger button isn't disabled before toggling the open state - style the disabled speed dial trigger fab button using the current theme to support both light and dark themes - fix issue where the disabled background (mostly transparent) allowed opaque `md-fab-action-item`s to fade through - disabled fab buttons now have no `box-shadow` which is consistent with Angular Material - fix basic usage demo to not have a black hover/focus background as that doesn't work at all on dark theme and was confusing in light theme - add test for when the click occurs on the `md-icon` instead of the `md-button` - remove duplicate `$button-fab-width` variable Fixes #9467
1 parent da86e62 commit e7dfcc1

File tree

8 files changed

+102
-41
lines changed

8 files changed

+102
-41
lines changed

src/components/button/button.scss

+4
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ button.md-button::-moz-focus-inner {
145145
&.ng-hide, &.ng-leave {
146146
transition: none;
147147
}
148+
149+
&[disabled] {
150+
box-shadow: none;
151+
}
148152
}
149153

150154
&:not([disabled]) {
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
.text-capitalize {
22
text-transform: capitalize;
33
}
4-
5-
.md-fab:hover, .md-fab.md-focused {
6-
background-color: #000 !important;
7-
}
8-
94
p.note {
105
font-size: 1.2rem;
116
}
12-
137
.lock-size {
148
min-width: 300px;
159
min-height: 300px;
1610
width: 300px;
1711
height: 300px;
1812
margin-left: auto;
1913
margin-right: auto;
20-
}
14+
}

src/components/fabSpeedDial/fabController.js

+57-28
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
.controller('MdFabController', MdFabController);
66

77
function MdFabController($scope, $element, $animate, $mdUtil, $mdConstant, $timeout) {
8-
var vm = this;
8+
var ctrl = this;
99
var initialAnimationAttempts = 0;
1010

1111
// NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops
1212

13-
vm.open = function() {
13+
ctrl.open = function() {
1414
$scope.$evalAsync("vm.isOpen = true");
1515
};
1616

17-
vm.close = function() {
17+
ctrl.close = function() {
1818
// Async eval to avoid conflicts with existing digest loops
1919
$scope.$evalAsync("vm.isOpen = false");
2020

@@ -23,15 +23,16 @@
2323
};
2424

2525
// Toggle the open/close state when the trigger is clicked
26-
vm.toggle = function() {
26+
ctrl.toggle = function() {
2727
$scope.$evalAsync("vm.isOpen = !vm.isOpen");
2828
};
2929

3030
/*
3131
* AngularJS Lifecycle hook for newer AngularJS versions.
32-
* Bindings are not guaranteed to have been assigned in the controller, but they are in the $onInit hook.
32+
* Bindings are not guaranteed to have been assigned in the controller, but they are in the
33+
* $onInit hook.
3334
*/
34-
vm.$onInit = function() {
35+
ctrl.$onInit = function() {
3536
setupDefaults();
3637
setupListeners();
3738
setupWatchers();
@@ -47,10 +48,10 @@
4748

4849
function setupDefaults() {
4950
// Set the default direction to 'down' if none is specified
50-
vm.direction = vm.direction || 'down';
51+
ctrl.direction = ctrl.direction || 'down';
5152

5253
// Set the default to be closed
53-
vm.isOpen = vm.isOpen || false;
54+
ctrl.isOpen = ctrl.isOpen || false;
5455

5556
// Start the keyboard interaction at the first action
5657
resetActionIndex();
@@ -82,6 +83,10 @@
8283
}
8384

8485
var closeTimeout;
86+
87+
/**
88+
* @param {MouseEvent} event
89+
*/
8590
function parseEvents(event) {
8691
// If the event is a click, just handle it
8792
if (event.type == 'click') {
@@ -91,7 +96,7 @@
9196
// If we focusout, set a timeout to close the element
9297
if (event.type == 'focusout' && !closeTimeout) {
9398
closeTimeout = $timeout(function() {
94-
vm.close();
99+
ctrl.close();
95100
}, 100, false);
96101
}
97102

@@ -103,7 +108,7 @@
103108
}
104109

105110
function resetActionIndex() {
106-
vm.currentActionIndex = -1;
111+
ctrl.currentActionIndex = -1;
107112
}
108113

109114
function setupWatchers() {
@@ -179,8 +184,8 @@
179184
});
180185

181186
// TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
182-
// this breaks accessibility, especially on mobile, since you have no arrow keys to press
183-
// resetActionTabIndexes();
187+
// this breaks accessibility, especially on mobile, since you have no arrow keys to press
188+
// resetActionTabIndexes();
184189
}
185190

186191
function disableKeyboard() {
@@ -194,14 +199,14 @@
194199
var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions');
195200

196201
if (!closestTrigger && !closestActions) {
197-
vm.close();
202+
ctrl.close();
198203
}
199204
}
200205
}
201206

202207
function keyPressed(event) {
203208
switch (event.which) {
204-
case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false;
209+
case $mdConstant.KEY_CODE.ESCAPE: ctrl.close(); event.preventDefault(); return false;
205210
case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
206211
case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;
207212
case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false;
@@ -221,12 +226,12 @@
221226
var actions = resetActionTabIndexes();
222227

223228
// Increment/decrement the counter with restrictions
224-
vm.currentActionIndex = vm.currentActionIndex + direction;
225-
vm.currentActionIndex = Math.min(actions.length - 1, vm.currentActionIndex);
226-
vm.currentActionIndex = Math.max(0, vm.currentActionIndex);
229+
ctrl.currentActionIndex = ctrl.currentActionIndex + direction;
230+
ctrl.currentActionIndex = Math.min(actions.length - 1, ctrl.currentActionIndex);
231+
ctrl.currentActionIndex = Math.max(0, ctrl.currentActionIndex);
227232

228233
// Focus the element
229-
var focusElement = angular.element(actions[vm.currentActionIndex]).children()[0];
234+
var focusElement = angular.element(actions[ctrl.currentActionIndex]).children()[0];
230235
angular.element(focusElement).attr('tabindex', 0);
231236
focusElement.focus();
232237

@@ -248,52 +253,76 @@
248253
}
249254

250255
function doKeyLeft(event) {
251-
if (vm.direction === 'left') {
256+
if (ctrl.direction === 'left') {
252257
doActionNext(event);
253258
} else {
254259
doActionPrev(event);
255260
}
256261
}
257262

258263
function doKeyUp(event) {
259-
if (vm.direction === 'down') {
264+
if (ctrl.direction === 'down') {
260265
doActionPrev(event);
261266
} else {
262267
doActionNext(event);
263268
}
264269
}
265270

266271
function doKeyRight(event) {
267-
if (vm.direction === 'left') {
272+
if (ctrl.direction === 'left') {
268273
doActionPrev(event);
269274
} else {
270275
doActionNext(event);
271276
}
272277
}
273278

274279
function doKeyDown(event) {
275-
if (vm.direction === 'up') {
280+
if (ctrl.direction === 'up') {
276281
doActionPrev(event);
277282
} else {
278283
doActionNext(event);
279284
}
280285
}
281286

282-
function isTrigger(element) {
287+
/**
288+
* @param {Node} element
289+
* @returns {Node|null}
290+
*/
291+
function getClosestButton(element) {
292+
return $mdUtil.getClosest(element, 'button') || $mdUtil.getClosest(element, 'md-button');
293+
}
294+
295+
/**
296+
* @param {Node} element
297+
* @returns {Node|null}
298+
*/
299+
function getClosestTrigger(element) {
283300
return $mdUtil.getClosest(element, 'md-fab-trigger');
284301
}
285302

286-
function isAction(element) {
303+
/**
304+
* @param {Node} element
305+
* @returns {Node|null}
306+
*/
307+
function getClosestAction(element) {
287308
return $mdUtil.getClosest(element, 'md-fab-actions');
288309
}
289310

311+
/**
312+
* @param {MouseEvent} event
313+
*/
290314
function handleItemClick(event) {
291-
if (isTrigger(event.target)) {
292-
vm.toggle();
315+
var closestButton = event.target ? getClosestButton(event.target) : null;
316+
317+
// Check that the button in the trigger is not disabled
318+
if (closestButton && !closestButton.disabled) {
319+
if (getClosestTrigger(event.target)) {
320+
ctrl.toggle();
321+
}
293322
}
294323

295-
if (isAction(event.target)) {
296-
vm.close();
324+
if (getClosestAction(event.target)) {
325+
ctrl.close();
297326
}
298327
}
299328

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
md-fab-speed-dial.md-THEME_NAME-theme {
2+
md-fab-trigger .md-fab.md-button[disabled] {
3+
background-color: '{{foreground-4}}';
4+
}
5+
}

src/components/fabSpeedDial/fabSpeedDial.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145

146146
styles.transform = styles.webkitTransform = '';
147147
styles.transitionDelay = '';
148-
styles.opacity = 1;
148+
styles.opacity = ctrl.isOpen ? 1 : 0;
149149

150150
// Make the items closest to the trigger have the highest z-index
151151
styles.zIndex = (items.length - index) + startZIndex;

src/components/fabSpeedDial/fabSpeedDial.spec.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('<md-fab-speed-dial> directive', function() {
5353
build(
5454
'<md-fab-speed-dial>' +
5555
' <md-fab-trigger>' +
56-
' <md-button></md-button>' +
56+
' <md-button class="md-fab"></md-button>' +
5757
' </md-fab-trigger>' +
5858
'</md-fab-speed-dial>'
5959
);
@@ -78,6 +78,35 @@ describe('<md-fab-speed-dial> directive', function() {
7878
expect(controller.isOpen).toBe(false);
7979
}));
8080

81+
it('toggles the menu when the trigger icon is clicked', inject(function() {
82+
build(
83+
'<md-fab-speed-dial>' +
84+
' <md-fab-trigger>' +
85+
' <md-button class="md-fab"><md-icon md-svg-src="img/icons/menu.svg"></md-icon></md-button>' +
86+
' </md-fab-trigger>' +
87+
'</md-fab-speed-dial>'
88+
);
89+
90+
// Click to open
91+
var clickEvent = {
92+
type: 'click',
93+
target: element.find('md-icon')
94+
};
95+
element.triggerHandler(clickEvent);
96+
pageScope.$digest();
97+
98+
expect(controller.isOpen).toBe(true);
99+
100+
// Make sure to flush the timeout that ignores other events
101+
$timeout.flush();
102+
103+
// Click to close
104+
element.triggerHandler(clickEvent);
105+
pageScope.$digest();
106+
107+
expect(controller.isOpen).toBe(false);
108+
}));
109+
81110

82111
it('closes the menu when an action is clicked', inject(function() {
83112
build(

src/components/fabToolbar/fabToolbar.scss

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
$button-fab-width: rem(5.600) !default;
21
$icon-button-margin: rem(0.600) !default;
32

43
md-fab-toolbar {
@@ -105,7 +104,7 @@ md-fab-toolbar {
105104
}
106105

107106
/*
108-
* Hover styling
107+
* Open styling
109108
*/
110109
&.md-is-open {
111110
md-fab-trigger > button {

src/core/util/util.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -622,8 +622,9 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
622622
});
623623
},
624624

625-
/*
626-
* getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching nodeName
625+
/**
626+
* getClosest replicates jQuery.closest() to walk up the DOM tree until it finds a matching
627+
* nodeName.
627628
*
628629
* @param {Node} el Element to start walking the DOM from
629630
* @param {string|function} validateWith If a string is passed, it will be evaluated against

0 commit comments

Comments
 (0)