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

Commit

Permalink
feat(sidenav): add focus override option and demo
Browse files Browse the repository at this point in the history
Closes #1891
  • Loading branch information
Marcy Sutton committed Apr 7, 2015
1 parent 74601d0 commit 58343c0
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 10 deletions.
10 changes: 10 additions & 0 deletions src/components/sidenav/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ <h1 class="md-toolbar-tools">Sidenav Left</h1>
<p>
The left sidenav will 'lock open' on a medium (>=960px wide) device.
</p>
<p>
The right sidenav will focus on a specific child element.
</p>

<div>
<md-button ng-click="toggleLeft()"
Expand All @@ -52,6 +55,13 @@ <h1 class="md-toolbar-tools">Sidenav Left</h1>
<h1 class="md-toolbar-tools">Sidenav Right</h1>
</md-toolbar>
<md-content ng-controller="RightCtrl" class="md-padding">

<md-input-container>
<label for="testInput">Test input</label>
<input type="text" id="testInput"
ng-model="data" md-sidenav-focus>
</md-input-container>

<md-button ng-click="close()" class="md-primary">
Close Sidenav Right
</md-button>
Expand Down
4 changes: 4 additions & 0 deletions src/components/sidenav/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
angular.module('sidenavDemo1', ['ngMaterial'])

.controller('AppCtrl', function($scope, $timeout, $mdSidenav, $log) {
$scope.data;

$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");
Expand Down
50 changes: 40 additions & 10 deletions src/components/sidenav/sidenav.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ angular.module('material.components.sidenav', [
])
.factory('$mdSidenav', SidenavService )
.directive('mdSidenav', SidenavDirective)
.directive('mdSidenavFocus', SidenavFocusDirective)
.controller('$mdSidenavController', SidenavController);


Expand Down Expand Up @@ -79,7 +80,23 @@ function SidenavService($mdComponentRegistry, $q) {
};
};
}

/**
* @private
* @ngdoc directive
* @name mdSidenavFocus
* @module material.components.sidenav
* @restrict A
*
**/
function SidenavFocusDirective() {
return {
restrict: 'A',
require: '^mdSidenav',
link: function(scope, element, attr, sidenavCtrl) {
sidenavCtrl.focusElement(element);
}
};
}
/**
* @ngdoc directive
* @name mdSidenav
Expand All @@ -92,6 +109,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
* <hljs lang="html">
* <div layout="row" ng-controller="MyController">
Expand All @@ -109,7 +129,11 @@ function SidenavService($mdComponentRegistry, $q) {
* <md-sidenav md-component-id="right"
* md-is-locked-open="$mdMedia('min-width: 333px')"
* class="md-sidenav-right">
* Right Nav!
* <md-input-container>
* <label for="testInput">Test input</label>
* <input id="testInput" type="text"
* ng-model="data" md-sidenav-focus>
* </md-input-container>
* </md-sidenav>
* </div>
* </hljs>
Expand All @@ -136,6 +160,7 @@ function SidenavService($mdComponentRegistry, $q) {
* - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
*
*/
function SidenavDirective($timeout, $animate, $parse, $log, $mdMedia, $mdConstant, $compile, $mdTheming, $q, $document) {
return {
Expand Down Expand Up @@ -179,6 +204,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`
Expand Down Expand Up @@ -208,15 +234,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
$animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed').then(function(){
if (scope.isOpen) {
element.focus();
focusEl && focusEl.focus();
}
})
]);
Expand Down Expand Up @@ -257,14 +283,12 @@ 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
triggeringElement && triggeringElement.focus();
triggeringElement = null;
}

deferred.resolve(result);
});

Expand Down Expand Up @@ -307,16 +331,22 @@ 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

self.$toggleOpen = function() { return $q.when($scope.isOpen); };
self.isOpen = function() { return !!$scope.isOpen; };
self.isLockedOpen = function() { return !!$scope.isLockedOpen; };
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.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
}
Expand Down
37 changes: 37 additions & 0 deletions src/components/sidenav/sidenav.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<div>');
var markup = '<md-sidenav md-is-open="show">'+
'<md-input-container><label>Label</label>' +
'<input type="text" md-sidenav-focus>' +
'</md-input-container>' +
'<md-sidenav>';
var sidenavEl = angular.element(markup);
parent.append(sidenavEl);
$compile(parent)($rootScope);
$rootScope.$apply('show = true');

var focusEl = sidenavEl.find('input');
$animate.triggerCallbacks();
expect($document.activeElement).toBe(focusEl[0]);
}));

it('should focus on last md-sidenav-focus element', inject(function($rootScope, $animate, $document, $compile) {
TestUtil.mockElementFocus(this);
var parent = angular.element('<div>');
var markup = '<md-sidenav md-is-open="show">'+
'<md-button md-sidenav-focus>Button</md-button>'+
'<md-input-container><label>Label</label>' +
'<input type="text" md-sidenav-focus>' +
'</md-input-container>' +
'<md-sidenav>';
var sidenavEl = angular.element(markup);
parent.append(sidenavEl);
$compile(parent)($rootScope);
$rootScope.$apply('show = true');

$animate.triggerCallbacks();
var focusEl = sidenavEl.find('input');
expect($document.activeElement).toBe(focusEl[0]);
}));

it('should lock open when is-locked-open is true', inject(function($rootScope, $animate, $document) {
var el = setup('md-is-open="show" md-is-locked-open="lock"');
expect(el.hasClass('md-locked-open')).toBe(false);
Expand Down

0 comments on commit 58343c0

Please sign in to comment.