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

fix(button): properly show focus effect #9826

Merged
merged 1 commit into from
Oct 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions src/components/button/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ function MdAnchorDirective($mdTheming) {
* </md-button>
* </hljs>
*/
function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $timeout) {
function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $mdInteraction) {

return {
restrict: 'EA',
Expand Down Expand Up @@ -162,20 +162,17 @@ function MdButtonDirective($mdButtonInkRipple, $mdTheming, $mdAria, $timeout) {
});

if (!element.hasClass('md-no-focus')) {
// restrict focus styles to the keyboard
scope.mouseActive = false;
element.on('mousedown', function() {
scope.mouseActive = true;
$timeout(function(){
scope.mouseActive = false;
}, 100);
})
.on('focus', function() {
if (scope.mouseActive === false) {

element.on('focus', function() {

// Only show the focus effect when being focused through keyboard interaction or programmatically
if (!$mdInteraction.isUserInvoked() || $mdInteraction.getLastInteractionType() === 'keyboard') {
element.addClass('md-focused');
}
})
.on('blur', function(ev) {

});

element.on('blur', function() {
Copy link
Member Author

@devversion devversion Oct 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This is intentionally another on statement, because otherwise the indention is ugly and this is just more clear..

element.removeClass('md-focused');
});
}
Expand Down
44 changes: 38 additions & 6 deletions src/components/button/button.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,52 @@ describe('md-button', function() {
expect(button.hasClass('md-button')).toBe(true);
}));

it('should not set focus state on mousedown', inject(function ($compile, $rootScope){
it('should apply focus effect with keyboard interaction', inject(function ($compile, $rootScope){
var button = $compile('<md-button>')($rootScope.$new());
var body = angular.element(document.body);

$rootScope.$apply();
button.triggerHandler('mousedown');
expect(button[0]).not.toHaveClass('md-focused');

// Fake a keyboard interaction for the $mdInteraction service.
body.triggerHandler('keydown');
button.triggerHandler('focus');

expect(button).toHaveClass('md-focused');

button.triggerHandler('blur');

expect(button).not.toHaveClass('md-focused');
}));

it('should set focus state on focus and remove on blur', inject(function ($compile, $rootScope){
it('should apply focus effect when programmatically focusing', inject(function ($compile, $rootScope){
var button = $compile('<md-button>')($rootScope.$new());

$rootScope.$apply();

button.triggerHandler('focus');
expect(button[0]).toHaveClass('md-focused');

expect(button).toHaveClass('md-focused');

button.triggerHandler('blur');
expect(button[0]).not.toHaveClass('md-focused');

expect(button).not.toHaveClass('md-focused');
}));

it('should not apply focus effect with mouse interaction', inject(function ($compile, $rootScope){
var button = $compile('<md-button>')($rootScope.$new());
var body = angular.element(document.body);

$rootScope.$apply();

// Fake a mouse interaction for the $mdInteraction service.
body.triggerHandler('mousedown');
button.triggerHandler('focus');

expect(button).not.toHaveClass('md-focused');

button.triggerHandler('blur');

expect(button).not.toHaveClass('md-focused');
}));

it('should not set the focus state if focus is disabled', inject(function($compile, $rootScope) {
Expand Down
23 changes: 20 additions & 3 deletions src/core/services/interaction/interaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ angular
* </hljs>
*
*/
function MdInteractionService($timeout) {
function MdInteractionService($timeout, $mdUtil) {
this.$timeout = $timeout;
this.$mdUtil = $mdUtil;

this.bodyElement = angular.element(document.body);
this.isBuffering = false;
this.bufferTimeout = null;
this.lastInteractionType = null;
this.lastInteractionTime = null;

// Type Mappings for the different events
// There will be three three interaction types
Expand Down Expand Up @@ -87,7 +89,7 @@ MdInteractionService.prototype.initializeEvents = function() {

/**
* Event listener for normal interaction events, which should be tracked.
* @param event {MouseEvent|KeyboardEvent|PointerEvent}
* @param event {MouseEvent|KeyboardEvent|PointerEvent|TouchEvent}
*/
MdInteractionService.prototype.onInputEvent = function(event) {
if (this.isBuffering) {
Expand All @@ -101,6 +103,7 @@ MdInteractionService.prototype.onInputEvent = function(event) {
}

this.lastInteractionType = type;
this.lastInteractionTime = this.$mdUtil.now();
};

/**
Expand Down Expand Up @@ -129,4 +132,18 @@ MdInteractionService.prototype.onBufferInputEvent = function(event) {
*/
MdInteractionService.prototype.getLastInteractionType = function() {
return this.lastInteractionType;
};
};

/**
* @ngdoc method
* @name $mdInteraction#isUserInvoked
* @description Method to detect whether any interaction happened recently or not.
* @param {number=} checkDelay Time to check for any interaction to have been triggered.
* @returns {boolean} Whether there was any interaction or not.
*/
MdInteractionService.prototype.isUserInvoked = function(checkDelay) {
var delay = angular.isNumber(checkDelay) ? checkDelay : 15;

// Check for any interaction to be within the specified check time.
return this.lastInteractionTime >= this.$mdUtil.now() - delay;
};
57 changes: 51 additions & 6 deletions src/core/services/interaction/interaction.spec.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
describe("$mdInteraction service", function() {
var $mdInteraction;

var $mdInteraction = null;
var bodyElement = null;

beforeEach(module('material.core'));

beforeEach(inject(function($injector) {
$mdInteraction = $injector.get('$mdInteraction');

bodyElement = angular.element(document.body);
}));

describe("last interaction type", function() {

var bodyElement = null;

beforeEach(function() {
bodyElement = angular.element(document.body);
});

it("should detect a keyboard interaction", function() {

Expand All @@ -30,4 +29,50 @@ describe("$mdInteraction service", function() {
});

});

describe('isUserInvoked', function() {

var element = null;

beforeEach(function() {
element = angular.element('<button>Click</button>');

bodyElement.append(element);
});

afterEach(function() {
element.remove();
});

it('should be true when programmatically focusing an element', function() {
element.focus();

expect($mdInteraction.isUserInvoked()).toBe(false);
});

it('should be false when focusing an element through keyboard', function() {

// Fake a focus event triggered by a keyboard interaction.
bodyElement.triggerHandler('keydown');
element.focus();

expect($mdInteraction.isUserInvoked()).toBe(true);
});

it('should allow passing a custom check delay', function(done) {
bodyElement.triggerHandler('keydown');

// The keyboard interaction is still in the same tick, so the interaction happened earlier than 15ms (as default)
expect($mdInteraction.isUserInvoked()).toBe(true);

setTimeout(function() {
// Expect the keyboard interaction to be older than 5ms (safer than exactly 10ms) as check time.
expect($mdInteraction.isUserInvoked(5)).toBe(false);

done();
}, 10);
});

});

});