diff --git a/src/components/sidenav/sidenav.js b/src/components/sidenav/sidenav.js
index 593a90ff066..da417398242 100644
--- a/src/components/sidenav/sidenav.js
+++ b/src/components/sidenav/sidenav.js
@@ -208,7 +208,7 @@ function SidenavFocusDirective() {
* - ``
* - `` (locks open on small screens)
*/
-function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document) {
+function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $mdInteraction, $animate, $compile, $parse, $log, $q, $document) {
return {
restrict: 'E',
scope: {
@@ -227,6 +227,7 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
*/
function postLink(scope, element, attr, sidenavCtrl) {
var lastParentOverFlow;
+ var triggeringInteractionType;
var triggeringElement = null;
var promise = $q.when(true);
@@ -289,6 +290,7 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
if ( isOpen ) {
// Capture upon opening..
triggeringElement = $document[0].activeElement;
+ triggeringInteractionType = $mdInteraction.getLastInteractionType();
}
disableParentScroll(isOpen);
@@ -344,9 +346,9 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
// When the current `updateIsOpen()` animation finishes
promise.then(function(result) {
- if ( !scope.isOpen ) {
+ if ( !scope.isOpen && triggeringElement && triggeringInteractionType && triggeringInteractionType === 'keyboard') {
// reset focus to originating element (if available) upon close
- triggeringElement && triggeringElement.focus();
+ triggeringElement.focus();
triggeringElement = null;
}
diff --git a/src/components/sidenav/sidenav.spec.js b/src/components/sidenav/sidenav.spec.js
index beee8374388..cea09b4f32e 100644
--- a/src/components/sidenav/sidenav.spec.js
+++ b/src/components/sidenav/sidenav.spec.js
@@ -173,6 +173,85 @@ describe('mdSidenav', function() {
});
+ describe("focus", function() {
+
+ var $material, $mdInteraction;
+
+ beforeEach( inject(function(_$material_, _$mdInteraction_) {
+ $material = _$material_;
+ $mdInteraction = _$mdInteraction_;
+ }));
+
+ function flush() {
+ $material.flushInterimElement();
+ }
+
+ function setupTrigger() {
+ var el;
+ inject(function($compile, $rootScope) {
+ var parent = angular.element(document.body);
+ el = angular.element('');
+ parent.append(el);
+ $compile(parent)($rootScope);
+ $rootScope.$apply();
+ });
+ return el;
+ }
+
+ it("should restore after sidenav triggered by keyboard", function(done) {
+ var sidenavElement = setup('');
+ var triggerElement = setupTrigger();
+ var controller = sidenavElement.controller('mdSidenav');
+
+ triggerElement.focus();
+
+ var evt = document.createEvent("KeyboardEvent");
+ evt.initEvent("keydown", true, true, window, 0, 0, 0, 0, 13, 13);
+ triggerElement[0].dispatchEvent(evt);
+
+ controller.$toggleOpen(true);
+ flush();
+
+ triggerElement.blur();
+
+ expect(document.activeElement).not.toBe(triggerElement[0]);
+
+ controller.$toggleOpen(false);
+ flush();
+
+ expect($mdInteraction.getLastInteractionType()).toBe("keyboard");
+ expect(document.activeElement).toBe(triggerElement[0]);
+ done();
+ });
+
+ it("should not restore after sidenav triggered by mouse", function(done) {
+ var sidenavElement = setup('');
+ var triggerElement = setupTrigger();
+ var controller = sidenavElement.controller('mdSidenav');
+
+ triggerElement.focus();
+
+ var event = document.createEvent("MouseEvent");
+ event.initMouseEvent("mousedown", true, true, window, null, 0, 0, 0, 0, false, false, false, false, 0, null);
+ triggerElement[0].dispatchEvent(event);
+
+ controller.$toggleOpen(true);
+ flush();
+
+ expect(document.activeElement).toBe(triggerElement[0]);
+
+ triggerElement.blur();
+
+ controller.$toggleOpen(false);
+ flush();
+
+ expect($mdInteraction.getLastInteractionType()).toBe("mouse");
+ expect(document.activeElement).not.toBe(triggerElement[0]);
+ done();
+ });
+
+ });
+
describe("controller Promise API", function() {
var $material, $rootScope;
@@ -186,7 +265,6 @@ describe('mdSidenav', function() {
$timeout = _$timeout_;
}));
-
it('should open(), close(), and toggle() with promises', function () {
var el = setup('');
var scope = el.isolateScope();
diff --git a/src/core/core.js b/src/core/core.js
index b75effcf632..d7db626010b 100644
--- a/src/core/core.js
+++ b/src/core/core.js
@@ -7,6 +7,7 @@ angular
'ngAnimate',
'material.core.animate',
'material.core.layout',
+ 'material.core.interaction',
'material.core.gestures',
'material.core.theming'
])
diff --git a/src/core/services/interaction/interaction.js b/src/core/services/interaction/interaction.js
new file mode 100644
index 00000000000..2248fd450f4
--- /dev/null
+++ b/src/core/services/interaction/interaction.js
@@ -0,0 +1,58 @@
+angular
+ .module('material.core.interaction', [])
+ .service('$mdInteraction', MdInteractionService);
+
+function MdInteractionService($timeout) {
+ var body = angular.element(document.body);
+ var _mouseEvent = window.MSPointerEvent ? 'MSPointerDown' : window.PointerEvent ? 'pointerdown' : 'mousedown';
+ var buffer = false;
+ var timer;
+ var lastInteractionType;
+ var inputMap = {
+ 'keydown': 'keyboard',
+ 'mousedown': 'mouse',
+ 'mouseenter': 'mouse',
+ 'touchstart': 'touch',
+ 'pointerdown': 'pointer',
+ 'MSPointerDown': 'pointer'
+ };
+ var pointerMap = {
+ 2: 'touch',
+ 3: 'touch',
+ 4: 'mouse'
+ };
+
+ function onInput(event) {
+ if (buffer) return;
+ var type = inputMap[event.type];
+ if (type === 'pointer') {
+ type = (typeof event.pointerType === 'number') ? pointerMap[event.pointerType] : event.pointerType;
+ }
+ lastInteractionType = type;
+ }
+
+ function onBufferInput(event) {
+ $timeout.cancel(timer);
+
+ onInput(event);
+ buffer = true;
+
+ timer = $timeout(function() {
+ buffer = false;
+ }, 1000);
+ }
+
+ body.on('keydown', onInput);
+ body.on(_mouseEvent, onInput);
+ body.on('mouseenter', onInput);
+ if ('ontouchstart' in document.documentElement) body.on('touchstart', onBufferInput);
+
+ /**
+ * Gets the last interaction type triggered by body.
+ * Possible values for return are `mouse`, `keyboard` and `touch`
+ * @returns {string}
+ */
+ this.getLastInteractionType = function() {
+ return lastInteractionType;
+ }
+}
\ No newline at end of file
diff --git a/src/core/services/interaction/interaction.spec.js b/src/core/services/interaction/interaction.spec.js
new file mode 100644
index 00000000000..f99f61d3626
--- /dev/null
+++ b/src/core/services/interaction/interaction.spec.js
@@ -0,0 +1,26 @@
+describe("$mdInteraction", function() {
+ beforeEach(module('material.core'));
+
+ describe("last interaction type", function() {
+
+ it("imitates a basic keyboard interaction and checks it", inject(function($mdInteraction) {
+
+ var event = document.createEvent('Event');
+ event.keyCode = 37;
+ event.initEvent('keydown', false, true);
+ document.body.dispatchEvent(event);
+
+ expect($mdInteraction.getLastInteractionType()).toBe('keyboard');
+ }));
+
+ it("dispatches a mousedown event on the document body and checks it", inject(function($mdInteraction) {
+
+ var event = document.createEvent("MouseEvent");
+ event.initMouseEvent("mousedown", true, true, window, null, 0, 0, 0, 0, false, false, false, false, 0, null);
+ document.body.dispatchEvent(event);
+
+ expect($mdInteraction.getLastInteractionType()).toBe("mouse");
+ }));
+
+ });
+});
\ No newline at end of file