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. Closes #2108.

s
  • Loading branch information
Marcy Sutton authored and ThomasBurleson committed Apr 8, 2015
1 parent bc37cec commit 3ca2a63
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 16 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">
<form>
<md-input-container>
<label for="testInput">Test input</label>
<input type="text" id="testInput"
ng-model="data" md-sidenav-focus>
</md-input-container>
</form>
<md-button ng-click="close()" class="md-primary">
Close Sidenav Right
</md-button>
Expand Down
3 changes: 3 additions & 0 deletions src/components/sidenav/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
69 changes: 53 additions & 16 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 All @@ -33,37 +34,27 @@ angular.module('material.components.sidenav', [
* $mdSidenav(componentId).then(function(instance) {
* $log.debug( componentId + "is now ready" );
* });
* </hljs>
* <hljs lang="js">
* // Async toggle the given sidenav;
* // when instance is known ready and lazy lookup is not needed.
* $mdSidenav(componentId)
* .toggle()
* .then(function(){
* $log.debug('toggled');
* });
* </hljs>
* <hljs lang="js">
* // Async open the given sidenav
* $mdSidenav(componentId)
* .open();
* .then(function(){
* $log.debug('opened');
* });
* </hljs>
* <hljs lang="js">
* // Async close the given sidenav
* $mdSidenav(componentId)
* .close();
* .then(function(){
* $log.debug('closed');
* });
* </hljs>
* <hljs lang="js">
* // Sync check to see if the specified sidenav is set to be open
* $mdSidenav(componentId).isOpen();
* </hljs>
* <hljs lang="js">
* // Sync check to whether given sidenav is locked open
* // If this is true, the sidenav will be open regardless of close()
* $mdSidenav(componentId).isLockedOpen();
Expand Down Expand Up @@ -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
* <hljs lang="html">
* <md-sidenav>
* <form>
* <md-input-container>
* <label for="testInput">Label</label>
* <input id="testInput" type="text" md-sidenav-focus>
* </md-input-container>
* </form>
* </md-sidenav>
* </hljs>
**/
function SidenavFocusDirective() {
return {
restrict: 'A',
require: '^mdSidenav',
link: function(scope, element, attr, sidenavCtrl) {
sidenavCtrl.focusElement(element);
}
};
}
/**
* @ngdoc directive
* @name mdSidenav
Expand All @@ -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
* <hljs lang="html">
* <div layout="row" ng-controller="MyController">
Expand All @@ -152,7 +175,13 @@ function SidenavService($mdComponentRegistry, $q) {
* <md-sidenav md-component-id="right"
* md-is-locked-open="$mdMedia('min-width: 333px')"
* class="md-sidenav-right">
* Right Nav!
* <form>
* <md-input-container>
* <label for="testInput">Test input</label>
* <input id="testInput" type="text"
* ng-model="data" md-sidenav-focus>
* </md-input-container>
* </form>
* </md-sidenav>
* </div>
* </hljs>
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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();
}
})
]);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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); };

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 3ca2a63

Please sign in to comment.