diff --git a/src/components/sidenav/demoBasicUsage/index.html b/src/components/sidenav/demoBasicUsage/index.html
index e672ad2033a..5098a8ed505 100644
--- a/src/components/sidenav/demoBasicUsage/index.html
+++ b/src/components/sidenav/demoBasicUsage/index.html
@@ -26,6 +26,9 @@
The left sidenav will 'lock open' on a medium (>=960px wide) device.
+
+ The right sidenav will focus on a specific child element.
+
Sidenav Left
+
Close Sidenav Right
diff --git a/src/components/sidenav/demoBasicUsage/script.js b/src/components/sidenav/demoBasicUsage/script.js
index 3d4d9e4b8b7..2d0736ffc3d 100644
--- a/src/components/sidenav/demoBasicUsage/script.js
+++ b/src/components/sidenav/demoBasicUsage/script.js
@@ -2,13 +2,16 @@
angular.module('sidenavDemo1', ['ngMaterial'])
.controller('AppCtrl', function($scope, $timeout, $mdSidenav, $log) {
+
$scope.toggleLeft = function() {
$mdSidenav('left').toggle()
.then(function(){
$log.debug("toggle left is done");
});
};
+
$scope.toggleRight = function() {
+
$mdSidenav('right').toggle()
.then(function(){
$log.debug("toggle RIGHT is done");
diff --git a/src/components/sidenav/sidenav.js b/src/components/sidenav/sidenav.js
index d4a64d265b1..d1a4bf57c27 100644
--- a/src/components/sidenav/sidenav.js
+++ b/src/components/sidenav/sidenav.js
@@ -14,6 +14,7 @@ angular.module('material.components.sidenav', [
])
.factory('$mdSidenav', SidenavService )
.directive('mdSidenav', SidenavDirective)
+ .directive('mdSidenavFocus', SidenavFocusDirective)
.controller('$mdSidenavController', SidenavController);
@@ -33,8 +34,6 @@ angular.module('material.components.sidenav', [
* $mdSidenav(componentId).then(function(instance) {
* $log.debug( componentId + "is now ready" );
* });
- *
- *
* // Async toggle the given sidenav;
* // when instance is known ready and lazy lookup is not needed.
* $mdSidenav(componentId)
@@ -42,28 +41,20 @@ angular.module('material.components.sidenav', [
* .then(function(){
* $log.debug('toggled');
* });
- *
- *
* // Async open the given sidenav
* $mdSidenav(componentId)
* .open();
* .then(function(){
* $log.debug('opened');
* });
- *
- *
* // Async close the given sidenav
* $mdSidenav(componentId)
* .close();
* .then(function(){
* $log.debug('closed');
* });
- *
- *
* // Sync check to see if the specified sidenav is set to be open
* $mdSidenav(componentId).isOpen();
- *
- *
* // Sync check to whether given sidenav is locked open
* // If this is true, the sidenav will be open regardless of close()
* $mdSidenav(componentId).isLockedOpen();
@@ -122,7 +113,36 @@ function SidenavService($mdComponentRegistry, $q) {
}
};
}
-
+/**
+ * @private
+ * @name mdSidenavFocus
+ * @restrict A
+ *
+ * @description
+ * `$mdSidenavFocus` provides a way to specify the focused element when a sidenav opens.
+ * This is completely optional, as the sidenav itself is focused by default.
+ *
+ * @usage
+ *
+ *
+ *
+ *
+ *
+ **/
+function SidenavFocusDirective() {
+ return {
+ restrict: 'A',
+ require: '^mdSidenav',
+ link: function(scope, element, attr, sidenavCtrl) {
+ sidenavCtrl.focusElement(element);
+ }
+ };
+}
/**
* @ngdoc directive
* @name mdSidenav
@@ -135,6 +155,9 @@ function SidenavService($mdComponentRegistry, $q) {
*
* By default, upon opening it will slide out on top of the main content area.
*
+ * For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
+ * It can be overridden with the `md-sidenav-focus` directive on the child element you want focused.
+ *
* @usage
*
*
@@ -152,7 +175,13 @@ function SidenavService($mdComponentRegistry, $q) {
*
- * Right Nav!
+ *
*
*
*
@@ -225,6 +254,7 @@ function SidenavDirective($timeout, $animate, $parse, $log, $mdMedia, $mdConstan
// Publish special accessor for the Controller instance
sidenavCtrl.$toggleOpen = toggleOpen;
+ sidenavCtrl.focusElement( sidenavCtrl.focusElement() || element );
/**
* Toggle the DOM classes to indicate `locked`
@@ -254,15 +284,15 @@ function SidenavDirective($timeout, $animate, $parse, $log, $mdMedia, $mdConstan
// Capture upon opening..
triggeringElement = $document[0].activeElement;
}
+ var focusEl = sidenavCtrl.focusElement();
disableParentScroll(isOpen);
return promise = $q.all([
$animate[isOpen ? 'enter' : 'leave'](backdrop, parent),
$animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed').then(function() {
- // If we opened, and haven't closed again before the animation finished
if (scope.isOpen) {
- element.focus();
+ focusEl && focusEl.focus();
}
})
]);
@@ -303,7 +333,7 @@ function SidenavDirective($timeout, $animate, $parse, $log, $mdMedia, $mdConstan
$timeout(function() {
// When the current `updateIsOpen()` animation finishes
- promise.then(function(result){
+ promise.then(function(result) {
if ( !scope.isOpen ) {
// reset focus to originating element (if available) upon close
@@ -353,7 +383,8 @@ function SidenavDirective($timeout, $animate, $parse, $log, $mdMedia, $mdConstan
*/
function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
- var self = this;
+ var self = this,
+ focusElement;
// Use Default internal method until overridden by directive postLink
@@ -365,6 +396,12 @@ function SidenavController($scope, $element, $attrs, $mdComponentRegistry, $q) {
self.open = function() { return self.$toggleOpen( true ); };
self.close = function() { return self.$toggleOpen( false ); };
self.toggle = function() { return self.$toggleOpen( !$scope.isOpen ); };
+ self.focusElement = function(el) {
+ if ( angular.isDefined(el) ) {
+ focusElement = el;
+ }
+ return focusElement;
+ };
self.$toggleOpen = function() { return $q.when($scope.isOpen); };
diff --git a/src/components/sidenav/sidenav.spec.js b/src/components/sidenav/sidenav.spec.js
index 6ff74f46609..70f4a6aab32 100644
--- a/src/components/sidenav/sidenav.spec.js
+++ b/src/components/sidenav/sidenav.spec.js
@@ -61,6 +61,43 @@ describe('mdSidenav', function() {
expect($document.activeElement).toBe(el[0]);
}));
+ it('should focus child directive with md-sidenav-focus', inject(function($rootScope, $animate, $document, $compile) {
+ TestUtil.mockElementFocus(this);
+ var parent = angular.element('