From 32c0fe1891eb658bc57082de0c836c98e99c22cf Mon Sep 17 00:00:00 2001 From: Topher Fangio Date: Tue, 27 Oct 2015 17:42:44 -0600 Subject: [PATCH] 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. Closes #5396. --- src/components/fabSpeedDial/fabController.js | 66 ++++++++++--------- src/components/fabSpeedDial/fabSpeedDial.js | 23 +++++-- src/components/fabSpeedDial/fabSpeedDial.scss | 11 +++- .../fabSpeedDial/fabSpeedDial.spec.js | 56 ++-------------- 4 files changed, 66 insertions(+), 90 deletions(-) diff --git a/src/components/fabSpeedDial/fabController.js b/src/components/fabSpeedDial/fabController.js index 226b764a81b..01ab1b88ae9 100644 --- a/src/components/fabSpeedDial/fabController.js +++ b/src/components/fabSpeedDial/fabController.js @@ -44,11 +44,7 @@ function setupListeners() { var eventTypes = [ - '$md.pressdown', - - 'click', // Fired via keyboard ENTER - - 'focusin', 'focusout' + 'click', 'focusin', 'focusout' ]; // Add our listeners @@ -68,34 +64,25 @@ }); } - var recentEvent; + var closeTimeout; function parseEvents(event) { - // If we've had a recent press/click event, or material is sending us an additional event, - // ignore it - if (recentEvent && (isClick(recentEvent) || recentEvent.$material)) { - return; - } - - // Otherwise, handle our events - if (isClick(event)) { + // If the event is a click, just handle it + if (event.type == 'click') { handleItemClick(event); - - // Store our recent click event - recentEvent = event; - } else if (event.type == 'focusin') { - vm.open(); - } else if (event.type == 'focusout') { - vm.close(); } - // Clear the recent event after all others have fired so we stop ignoring - $timeout(function() { - recentEvent = null; - }, 100, false); - } + // If we focusout, set a timeout to close the element + if (event.type == 'focusout' && !closeTimeout) { + closeTimeout = $timeout(function() { + vm.close(); + }, 100, false); + } - function isClick(event) { - return event.type == '$md.pressdown' || event.type == 'click'; + // If we see a focusin and there is a timeout about to run, cancel it so we stay open + if (event.type == 'focusin' && closeTimeout) { + $timeout.cancel(closeTimeout); + closeTimeout = null; + } } function resetActionIndex() { @@ -154,19 +141,34 @@ } function enableKeyboard() { - angular.element(document).on('keydown', keyPressed); + $element.on('keydown', keyPressed); + angular.element(document).on('click', checkForOutsideClick); + angular.element(document).on('touchend', checkForOutsideClick); - // TODO: On desktop, we should be able to reset the indexes so you cannot tab through + // TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but + // this breaks accessibility, especially on mobile, since you have no arrow keys to press //resetActionTabIndexes(); } function disableKeyboard() { - angular.element(document).off('keydown', keyPressed); + $element.off('keydown', keyPressed); + angular.element(document).off('click', checkForOutsideClick); + angular.element(document).off('touchend', checkForOutsideClick); + } + + function checkForOutsideClick(event) { + if (event.target) { + var closestTrigger = $mdUtil.getClosest(event.target, 'md-fab-trigger'); + var closestActions = $mdUtil.getClosest(event.target, 'md-fab-actions'); + + if (!closestTrigger && !closestActions) { + vm.close(); + } + } } function keyPressed(event) { switch (event.which) { - case $mdConstant.KEY_CODE.SPACE: event.preventDefault(); return false; case $mdConstant.KEY_CODE.ESCAPE: vm.close(); event.preventDefault(); return false; case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false; case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false; diff --git a/src/components/fabSpeedDial/fabSpeedDial.js b/src/components/fabSpeedDial/fabSpeedDial.js index b274c70e185..bd9c29b23cd 100644 --- a/src/components/fabSpeedDial/fabSpeedDial.js +++ b/src/components/fabSpeedDial/fabSpeedDial.js @@ -1,6 +1,13 @@ (function() { 'use strict'; + /** + * The duration of the CSS animation in milliseconds. + * + * @type {number} + */ + var cssAnimationDuration = 300; + /** * @ngdoc module * @name material.components.fabSpeedDial @@ -103,7 +110,9 @@ } } - function MdFabSpeedDialFlingAnimation() { + function MdFabSpeedDialFlingAnimation($timeout) { + function delayDone(done) { $timeout(done, cssAnimationDuration, false); } + function runAnimation(element) { var el = element[0]; var ctrl = element.controller('mdFabSpeedDial'); @@ -169,17 +178,19 @@ addClass: function(element, className, done) { if (element.hasClass('md-fling')) { runAnimation(element); - done(); } + delayDone(done); }, removeClass: function(element, className, done) { runAnimation(element); - done(); + delayDone(done); } } } - function MdFabSpeedDialScaleAnimation() { + function MdFabSpeedDialScaleAnimation($timeout) { + function delayDone(done) { $timeout(done, cssAnimationDuration, false); } + var delay = 65; function runAnimation(element) { @@ -210,12 +221,12 @@ return { addClass: function(element, className, done) { runAnimation(element); - done(); + delayDone(done); }, removeClass: function(element, className, done) { runAnimation(element); - done(); + delayDone(done); } } } diff --git a/src/components/fabSpeedDial/fabSpeedDial.scss b/src/components/fabSpeedDial/fabSpeedDial.scss index 9745a7376a9..7ccf927927d 100644 --- a/src/components/fabSpeedDial/fabSpeedDial.scss +++ b/src/components/fabSpeedDial/fabSpeedDial.scss @@ -31,7 +31,6 @@ md-fab-speed-dial { &.md-is-open { .md-fab-action-item { - visibility: visible; align-items: center; } } @@ -43,7 +42,6 @@ md-fab-speed-dial { height: auto; .md-fab-action-item { - visibility: hidden; transition: $swift-ease-in; } } @@ -108,6 +106,15 @@ md-fab-speed-dial { } } + /* + * Hide some graphics glitches if switching animation types + */ + &.md-fling-remove, &.md-scale-remove { + .md-fab-action-item > * { + visibility: hidden; + } + } + /* * Handle the animations */ diff --git a/src/components/fabSpeedDial/fabSpeedDial.spec.js b/src/components/fabSpeedDial/fabSpeedDial.spec.js index cbc496bc537..3182437f177 100644 --- a/src/components/fabSpeedDial/fabSpeedDial.spec.js +++ b/src/components/fabSpeedDial/fabSpeedDial.spec.js @@ -51,7 +51,7 @@ describe(' directive', function() { it('toggles the menu when the trigger clicked', inject(function() { build( - '' + + '' + ' ' + ' ' + ' ' + @@ -107,55 +107,7 @@ describe(' directive', function() { expect(controller.isOpen).toBe(false); })); - it('opens the menu when the trigger is focused', inject(function() { - build( - '' + - ' ' + - ' ' + - ' ' + - '' - ); - - var focusEvent = { - type: 'focusin', - target: element.find('md-fab-trigger').find('md-button') - }; - - element.triggerHandler(focusEvent); - pageScope.$digest(); - expect(controller.isOpen).toBe(true); - })); - - it('closes the menu when the trigger is blurred', inject(function() { - build( - '' + - ' ' + - ' ' + - ' ' + - '' - ); - - var focusInEvent = { - type: 'focusin', - target: element.find('md-fab-trigger').find('md-button') - }; - - var focusOutEvent = { - type: 'focusout', - target: element.find('md-fab-trigger').find('md-button') - }; - - element.triggerHandler(focusInEvent); - pageScope.$digest(); - expect(controller.isOpen).toBe(true); - - element.triggerHandler(focusOutEvent); - pageScope.$digest(); - expect(controller.isOpen).toBe(false); - })); - - - it('properly finishes the fling animation', inject(function(mdFabSpeedDialFlingAnimation) { + it('properly finishes the fling animation', inject(function(mdFabSpeedDialFlingAnimation, $timeout) { build( '' + ' ' + @@ -167,9 +119,11 @@ describe(' directive', function() { var removeDone = jasmine.createSpy('removeDone'); mdFabSpeedDialFlingAnimation.addClass(element, 'md-is-open', addDone); + $timeout.flush(); expect(addDone).toHaveBeenCalled(); mdFabSpeedDialFlingAnimation.removeClass(element, 'md-is-open', removeDone); + $timeout.flush(); expect(removeDone).toHaveBeenCalled(); })); @@ -185,9 +139,11 @@ describe(' directive', function() { var removeDone = jasmine.createSpy('removeDone'); mdFabSpeedDialScaleAnimation.addClass(element, 'md-is-open', addDone); + $timeout.flush(); expect(addDone).toHaveBeenCalled(); mdFabSpeedDialScaleAnimation.removeClass(element, 'md-is-open', removeDone); + $timeout.flush(); expect(removeDone).toHaveBeenCalled(); }));