Skip to content

Commit

Permalink
restrict tab navigation within the modal
Browse files Browse the repository at this point in the history
  • Loading branch information
sarbbottam committed Aug 9, 2015
1 parent 2e0bc3a commit 1993fda
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 7 deletions.
4 changes: 3 additions & 1 deletion example/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var modalContainer = document.getElementById('modal');
var primaryButton = modalContainer.querySelector('.js-button-primary');
var secondaryButton = modalContainer.querySelector('.js-button-secondary');
var closeButton = modalContainer.querySelector('.js-button-close');
var focusableNodeList = Array.prototype.slice.call(modalContainer.querySelectorAll('.js-focusable'));

var showButton = document.getElementById('show-modal');

Expand All @@ -15,7 +16,8 @@ modal = new Modal({
modalContainer: modalContainer,
primaryButton: primaryButton,
secondaryButton: secondaryButton,
closeButton: closeButton
closeButton: closeButton,
focusableNodeList: focusableNodeList
});

modal.init();
Expand Down
6 changes: 3 additions & 3 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</div>
<div id="modal" class="modal hide" aria-labelledby="modal-heading" aria-describedby="modal-description" role="dialog">
<div class="modal-content p-a v-a-m">
<input id="button-close" class="button modal-close-button js-button-close" type="button" value="X" aria-label="close modal">
<input id="button-close" class="button modal-close-button js-button-close js-focusable" type="button" value="X" aria-label="close modal">
<div id="modal-description" class="clipped">Beginning of dialog window. It begins with a heading 1 called "Modal Heading". Escape will cancel and close the window.</div>
<div class="modal-header">
<h1 id="modal-heading" class="text-lg">Modal Heading</h1>
Expand All @@ -36,10 +36,10 @@ <h1 id="modal-heading" class="text-lg">Modal Heading</h1>
</div>
<div class="row modal-footer">
<div class="col col-1-2 p-e">
<input type="button" class="button button-primary w-100 js-button-primary" value="OK">
<input type="button" class="button button-primary w-100 js-button-primary js-focusable" value="OK">
</div>
<div class="col col-1-2 p-s">
<input type="button" class="button button-secondary w-100 js-button-secondary" value="Cancel">
<input type="button" class="button button-secondary w-100 js-button-secondary js-focusable" value="Cancel">
</div>
</div>
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ function Modal(config) {
this.primaryFunction = config.primaryFunction;
this.secondaryFunction = config.secondaryFunction;

this.focusableNodeList = config.focusableNodeList || null;
this.focusableIndex = -1;

css = config.css;

/* istanbul ignore next */
Expand All @@ -24,10 +27,31 @@ function Modal(config) {
}

function keydownHandler(e) {
var focusableNodeList = this.focusableNodeList;
var focusableIndex = this.focusableIndex;

if (e.keyCode === 27) {
this.hide();
document.removeEventListener('keydown', this.keydownHandler);
}

if (focusableNodeList && e.keyCode === 9) {
e.preventDefault();
e.stopPropagation();
if (e.shiftKey) {
focusableIndex -= 1;
if (focusableIndex < 0) {
focusableIndex = focusableNodeList.length - 1;
}
} else {
focusableIndex += 1;
if (focusableIndex > focusableNodeList.length - 1) {
focusableIndex = 0;
}
}
focusableNodeList[focusableIndex].focus();
this.focusableIndex = focusableIndex;
}
}

Modal.prototype.show = function() {
Expand Down
39 changes: 36 additions & 3 deletions test/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('Modal', function() {
<div id="main"></div> \
<div id="modal" class="modal hide" aria-labelledby="modal-heading" aria-describedby="modal-description" role="dialog"> \
<div class="modal-content v-a-m"> \
<input id="button-close" class="button button-close js-button-close" type="button" value="X" aria-label="close modal"> \
<input id="button-close" class="button button-close js-button-close js-focusable" type="button" value="X" aria-label="close modal"> \
<div id="modal-description" class="clipped">Beginning of dialog window. It begins with a heading 1 called "Modal Heading". Escape will cancel and close the window.</div> \
<div class="modal-header"> \
<h1 id="modal-heading" class="text-lg">Modal Heading</h1> \
Expand All @@ -15,10 +15,10 @@ describe('Modal', function() {
</div> \
<div class="row modal-footer"> \
<div class="col col-1-2 p-e"> \
<input type="button" class="button button-primary w-100 js-button-primary" value="OK"> \
<input type="button" class="button button-primary w-100 js-button-primary js-focusable" value="OK"> \
</div> \
<div class="col col-1-2 p-s"> \
<input type="button" class="button button-secondary w-100 js-button-secondary" value="Cancel"> \
<input type="button" class="button button-secondary w-100 js-button-secondary js-focusable" value="Cancel"> \
</div> \
</div> \
</div> \
Expand All @@ -38,6 +38,8 @@ describe('Modal', function() {
var primaryFunction;
var secondaryFunction;

var focusableNodeList;

before(function() {
wrapper = document.createElement('div');
wrapper.innerHTML = markup;
Expand All @@ -50,6 +52,8 @@ describe('Modal', function() {
secondaryButton = modalContainer.querySelector('.js-button-secondary');
closeButton = modalContainer.querySelector('.js-button-close');

focusableNodeList = Array.prototype.slice.call(modalContainer.querySelectorAll('.js-focusable'));

primaryFunction = function noop() {};
secondaryFunction = function noop() {};
modal = new Modal({
Expand Down Expand Up @@ -156,4 +160,33 @@ describe('Modal', function() {

});

describe('tab navigation', function() {

it('should focus only the nodes withing the modal', function() {
modal = new Modal({
mainContainer: mainContainer,
modalContainer: modalContainer,
primaryButton: primaryButton,
secondaryButton: secondaryButton,
closeButton: closeButton,
focusableNodeList: focusableNodeList
});
modal.init();
modal.show();
event.triggerKeydownEvent(modalContainer, 9);
assert.equal(document.activeElement, closeButton);
event.triggerKeydownEvent(modalContainer, 9);
assert.equal(document.activeElement, primaryButton);
event.triggerKeydownEvent(modalContainer, 9);
assert.equal(document.activeElement, secondaryButton);
event.triggerKeydownEvent(modalContainer, 9);
assert.equal(document.activeElement, closeButton);
event.triggerKeydownEvent(modalContainer, 9, true);
assert.equal(document.activeElement, secondaryButton);
event.triggerKeydownEvent(modalContainer, 9, true);
assert.equal(document.activeElement, primaryButton);
});

});

});

0 comments on commit 1993fda

Please sign in to comment.