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

Commit

Permalink
fix(fab-speed-dial): keyboard navigation issues with tab and shift+tab
Browse files Browse the repository at this point in the history
- add code to handle Tab and Shift+Tab keyboard interactions
- rip out old an inaccessible `tabindex` shifting code
- use a more noticeable background color for raised mini fab action buttons
  in speed dials as compared to stand-alone raised mini fab buttons
- change fab-speed-dial demo from branded SVGs with hard coded colors to
  Material Design icons that can be styled by color
- add button demo of `md-raised md-fab`
- move from old `vm` controller alias to more consistent `ctrl`

Fixes #12043
  • Loading branch information
Splaktar committed Dec 17, 2020
1 parent e7dfcc1 commit 41c71ed
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/components/button/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<md-icon md-svg-src="img/icons/ic_people_24px.svg"></md-icon>
</md-button>

<md-button class="md-fab md-mini" aria-label="Eat cake">
<md-button class="md-fab md-raised md-mini" aria-label="Eat cake">
<md-icon md-svg-src="img/icons/cake.svg"></md-icon>
</md-button>

Expand Down
12 changes: 6 additions & 6 deletions src/components/fabSpeedDial/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
</md-fab-trigger>

<md-fab-actions>
<md-button aria-label="Twitter" class="md-fab md-raised md-mini">
<md-icon md-svg-src="img/icons/twitter.svg" aria-label="Twitter"></md-icon>
<md-button aria-label="Play Demo" class="md-fab md-raised md-mini">
<md-icon md-svg-src="img/icons/ic_play_circle_fill_24px.svg" aria-label="Play Demo"></md-icon>
</md-button>
<md-button aria-label="Facebook" class="md-fab md-raised md-mini">
<md-icon md-svg-src="img/icons/facebook.svg" aria-label="Facebook"></md-icon>
<md-button aria-label="Video Tutorial" class="md-fab md-raised md-mini">
<md-icon md-svg-src="img/icons/ic_ondemand_video_24px.svg" aria-label="Video Tutorial"></md-icon>
</md-button>
<md-button aria-label="Google Hangout" class="md-fab md-raised md-mini">
<md-icon md-svg-src="img/icons/hangout.svg" aria-label="Google Hangout"></md-icon>
<md-button aria-label="View Code" class="md-fab md-raised md-mini">
<md-icon md-svg-src="img/icons/ic_code_24px.svg" aria-label="View Code"></md-icon>
</md-button>
</md-fab-actions>
</md-fab-speed-dial>
Expand Down
61 changes: 31 additions & 30 deletions src/components/fabSpeedDial/fabController.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
// NOTE: We use async eval(s) below to avoid conflicts with any existing digest loops

ctrl.open = function() {
$scope.$evalAsync("vm.isOpen = true");
$scope.$evalAsync("ctrl.isOpen = true");
};

ctrl.close = function() {
// Async eval to avoid conflicts with existing digest loops
$scope.$evalAsync("vm.isOpen = false");
$scope.$evalAsync("ctrl.isOpen = false");

// Focus the trigger when the element closes so users can still tab to the next item
$element.find('md-fab-trigger')[0].focus();
};

// Toggle the open/close state when the trigger is clicked
ctrl.toggle = function() {
$scope.$evalAsync("vm.isOpen = !vm.isOpen");
$scope.$evalAsync("ctrl.isOpen = !ctrl.isOpen");
};

/*
Expand Down Expand Up @@ -113,7 +113,7 @@

function setupWatchers() {
// Watch for changes to the direction and update classes/attributes
$scope.$watch('vm.direction', function(newDir, oldDir) {
$scope.$watch('ctrl.direction', function(newDir, oldDir) {
// Add the appropriate classes so we can target the direction in the CSS
$animate.removeClass($element, 'md-' + oldDir);
$animate.addClass($element, 'md-' + newDir);
Expand All @@ -125,7 +125,7 @@
var trigger, actions;

// Watch for changes to md-open
$scope.$watch('vm.isOpen', function(isOpen) {
$scope.$watch('ctrl.isOpen', function(isOpen) {
// Reset the action index since it may have changed
resetActionIndex();

Expand Down Expand Up @@ -182,10 +182,6 @@
$mdUtil.nextTick(function() {
angular.element(document).on('click touchend', checkForOutsideClick);
});

// TODO: On desktop, we should be able to reset the indexes so you cannot tab through, but
// this breaks accessibility, especially on mobile, since you have no arrow keys to press
// resetActionTabIndexes();
}

function disableKeyboard() {
Expand All @@ -204,13 +200,18 @@
}
}

/**
* @param {KeyboardEvent} event
* @returns {boolean}
*/
function keyPressed(event) {
switch (event.which) {
case $mdConstant.KEY_CODE.ESCAPE: ctrl.close(); event.preventDefault(); return false;
case $mdConstant.KEY_CODE.LEFT_ARROW: doKeyLeft(event); return false;
case $mdConstant.KEY_CODE.UP_ARROW: doKeyUp(event); return false;
case $mdConstant.KEY_CODE.RIGHT_ARROW: doKeyRight(event); return false;
case $mdConstant.KEY_CODE.DOWN_ARROW: doKeyDown(event); return false;
case $mdConstant.KEY_CODE.TAB: doShift(event); return false;
}
}

Expand All @@ -223,33 +224,25 @@
}

function focusAction(event, direction) {
var actions = resetActionTabIndexes();
var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item');
var previousActionIndex = ctrl.currentActionIndex;

// Increment/decrement the counter with restrictions
ctrl.currentActionIndex = ctrl.currentActionIndex + direction;
ctrl.currentActionIndex = Math.min(actions.length - 1, ctrl.currentActionIndex);
ctrl.currentActionIndex = Math.max(0, ctrl.currentActionIndex);

// Focus the element
var focusElement = angular.element(actions[ctrl.currentActionIndex]).children()[0];
angular.element(focusElement).attr('tabindex', 0);
focusElement.focus();

// Make sure the event doesn't bubble and cause something else
event.preventDefault();
event.stopImmediatePropagation();
}

function resetActionTabIndexes() {
// Grab all of the actions
var actions = getActionsElement()[0].querySelectorAll('.md-fab-action-item');

// Disable all other actions for tabbing
angular.forEach(actions, function(action) {
angular.element(angular.element(action).children()[0]).attr('tabindex', -1);
});
// Let Tab and Shift+Tab escape if we're trying to move past the start/end.
if (event.which !== $mdConstant.KEY_CODE.TAB ||
previousActionIndex !== ctrl.currentActionIndex) {
// Focus the element
var focusElement = angular.element(actions[ctrl.currentActionIndex]).children()[0];
focusElement.focus();

return actions;
// Make sure the event doesn't bubble and cause something else
event.preventDefault();
event.stopImmediatePropagation();
}
}

function doKeyLeft(event) {
Expand Down Expand Up @@ -284,6 +277,14 @@
}
}

function doShift(event) {
if (event.shiftKey) {
doActionPrev(event);
} else {
doActionNext(event);
}
}

/**
* @param {Node} element
* @returns {Node|null}
Expand All @@ -309,7 +310,7 @@
}

/**
* @param {MouseEvent} event
* @param {MouseEvent|FocusEvent} event
*/
function handleItemClick(event) {
var closestButton = event.target ? getClosestButton(event.target) : null;
Expand Down
8 changes: 8 additions & 0 deletions src/components/fabSpeedDial/fabSpeedDial-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ md-fab-speed-dial.md-THEME_NAME-theme {
md-fab-trigger .md-fab.md-button[disabled] {
background-color: '{{foreground-4}}';
}
md-fab-actions .md-fab-action-item {
.md-button.md-fab.md-raised.md-mini {
&:hover,
&.md-focused {
background-color: '{{background-500}}';
}
}
}
}
2 changes: 1 addition & 1 deletion src/components/fabSpeedDial/fabSpeedDial.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@

bindToController: true,
controller: 'MdFabController',
controllerAs: 'vm',
controllerAs: 'ctrl',

link: FabSpeedDialLink
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/fabToolbar/fabToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

bindToController: true,
controller: 'MdFabController',
controllerAs: 'vm',
controllerAs: 'ctrl',

link: link
};
Expand Down

0 comments on commit 41c71ed

Please sign in to comment.