Skip to content

Commit

Permalink
Add tabs widget example where selection does not follow focus
Browse files Browse the repository at this point in the history
Merge for pull request #269 that resolves issue #152.
  • Loading branch information
mcking65 committed Feb 9, 2017
2 parents 85aab94 + a6bc19d commit 3d6f84e
Show file tree
Hide file tree
Showing 7 changed files with 574 additions and 64 deletions.
103 changes: 53 additions & 50 deletions aria-practices.html

Large diffs are not rendered by default.

File renamed without changes.
File renamed without changes.
27 changes: 13 additions & 14 deletions examples/tabs/tabs.html → examples/tabs/tabs-1/tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,33 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Tabs | WAI-ARIA Authoring Practices 1.1</title>
<title>Tabs (Follows focus) | WAI-ARIA Authoring Practices 1.1</title>

<!-- Core js and css shared by all examples; do not modify when using this template. -->
<link href="../css/core.css" rel="stylesheet">
<link href="../css/table.css" rel="stylesheet">
<link href="../../css/core.css" rel="stylesheet">
<link href="../../css/table.css" rel="stylesheet">

<!-- js and css for this example. -->
<link href="css/tabs.css" rel="stylesheet">
</head>
<body>
<main>
<h1>Tabs</h1>
<h1>Tabs (Follows focus)</h1>

<p>
The below example section demonstrates a tabs widget that implements the <a href="../../#tabpanel">design pattern for tabs</a>.
This example section demonstrates a tabs widget that implements the <a href="../../../#tabpanel">design pattern for tabs</a>.
In this example panels are automatically activated when its tab receives focus.
</p>
<p>Similar examples include: </p>
<ul>
<li><a href="../tabs-2/tabs.html">Tabs (Manual activation)</a>: a version of the tabs widget where tabs have to be manually activated via <kbd>space</kbd> or <kbd>enter</kbd>.</li>
<!-- list other examples that implement the same design pattern. -->
</ul>

<section>
<h2 id="ex_label">Example</h2>
<div aria-labelledby="ex_label" role="region">
<!--
Note the ID of the following div that contains the example HTML is used as a parameter for the sourceCode.add() function.
The sourceCode functions in the examples/js/examples.js render the HTML source to show it to the reader of the example page.
If you change the ID of this div, be sure to update the parameters of the sourceCode.add() function call, which is made following the div with id="sc1" where the HTML is render.
The div for the rendered HTML source is in the last section of the page.
-->
<div id="ex1">
<!-- Replace content of this div with the example. -->
<div class="tabs">
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="nils-tab" id="nils">Nils Frahm</button>
Expand Down Expand Up @@ -67,7 +66,6 @@ <h2>Accessibility Features</h2>

<section>
<h2 id="kbd_label">Keyboard Support</h2>
<!-- Update the content of this table to describe which keys are implemented in this example. -->
<table aria-labelledby="kbd_label">
<thead>
<tr>
Expand Down Expand Up @@ -187,7 +185,8 @@ <h2 id="sc1_label">HTML Source Code</h2>
</main>

<nav>
<a href="../../#tabpanel">Tabs Design Pattern in WAI-ARIA Authoring Practices 1.1</a>
<!-- Update the pattern_ID parameter of this link to refer to the APG design pattern section related to this example. -->
<a href="../../../#tabpanel">Tabs Design Pattern in WAI-ARIA Authoring Practices 1.1</a>
</nav>


Expand Down
49 changes: 49 additions & 0 deletions examples/tabs/tabs-2/css/tabs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.tabs {
width: 20em;
font-family: "lucida grande", sans-serif;
}

[role="tab"] {
margin: 0 0 -2px;
border: 2px solid #444;
border-radius: 0.4em 0.4em 0 0;
font-family: inherit;
font-size: inherit;
background: #fff;
}

[role="tab"][aria-selected="true"] {
border-color: hsl(218, 96%, 38%);
color: #fff;
background: hsl(212, 96%, 38%);
outline: 0;
}

[role="tab"]:hover,
[role="tab"]:focus {
border-color: hsl(20, 96%, 38%);
color: #000;
background: hsl(20, 96%, 70%);
outline: 0;
}

[role="tabpanel"] {
position: relative;
z-index: 2;
padding: 0.5em;
border: 2px solid #444;
border-radius: 0 0.4em 0.4em 0.4em;
}

[role="tabpanel"]:focus {
border-color: hsl(20, 96%, 38%);
outline: 0;
}

[role="tabpanel"] p {
margin: 0;
}

[role="tabpanel"] * + p {
margin-top: 1em;
}
234 changes: 234 additions & 0 deletions examples/tabs/tabs-2/js/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
(function () {
var tablist = document.querySelectorAll('[role="tablist"]')[0];
var tabs;
var panels;

generateArrays();

function generateArrays () {
tabs = document.querySelectorAll('[role="tab"]');
panels = document.querySelectorAll('[role="tabpanel"]');
};

// For easy reference
var keys = {
end: 35,
home: 36,
left: 37,
up: 38,
right: 39,
down: 40,
delete: 46,
enter: 13,
space: 32
};

// Add or substract depenign on key pressed
var direction = {
37: -1,
38: -1,
39: 1,
40: 1
};

// Bind listeners
for (i = 0; i < tabs.length; ++i) {
addListeners(i);
};

function addListeners (index) {
tabs[index].addEventListener('click', clickEventListener);
tabs[index].addEventListener('keydown', keydownEventListener);
tabs[index].addEventListener('keyup', keyupEventListener);

// Build an array with all tabs (<button>s) in it
tabs[index].index = index;
};

// When a tab is clicked, activateTab is fired to activate it
function clickEventListener (event) {
var tab = event.target;
activateTab(tab, false);
};

// Handle keydown on tabs
function keydownEventListener (event) {
var key = event.keyCode;

switch (key) {
case keys.end:
event.preventDefault();
// Activate last tab
focusLastTab();
break;
case keys.home:
event.preventDefault();
// Activate first tab
focusFirstTab();
break;

// Up and down are in keydown
// because we need to prevent page scroll >:)
case keys.up:
case keys.down:
determineOrientation(event);
break;
};
};

// Handle keyup on tabs
function keyupEventListener (event) {
var key = event.keyCode;

switch (key) {
case keys.left:
case keys.right:
determineOrientation(event);
break;
case keys.delete:
determineDeletable(event);
break;
case keys.enter:
case keys.space:
activateTab(event.target);
break;
};
};

// When a tablist’s aria-orientation is set to vertical,
// only up and down arrow should function.
// In all other cases only left and right arrow function.
function determineOrientation (event) {
var key = event.keyCode;
var vertical = tablist.getAttribute('aria-orientation') == 'vertical';
var proceed = false;

if (vertical) {
if (key === keys.up || key === keys.down) {
event.preventDefault();
proceed = true;
};
}
else {
if (key === keys.left || key === keys.right) {
proceed = true;
};
};

if (proceed) {
switchTabOnArrowPress(event);
};
};

// Either focus the next, previous, first, or last tab
// depening on key pressed
function switchTabOnArrowPress (event) {
var pressed = event.keyCode;

if (direction[pressed]) {
var target = event.target;
if (target.index !== undefined) {
if (tabs[target.index + direction[pressed]]) {
tabs[target.index + direction[pressed]].focus();
}
else if (pressed === keys.left || pressed === keys.up) {
focusLastTab();
}
else if (pressed === keys.right || pressed == keys.down) {
focusFirstTab();
};
};
};
};

// Activates any given tab panel
function activateTab (tab, setFocus) {
setFocus = setFocus || true;
// Deactivate all other tabs
deactivateTabs();

// Remove tabindex attribute
tab.removeAttribute('tabindex');

// Set the tab as selected
tab.setAttribute('aria-selected', 'true');

// Get the value of aria-controls (which is an ID)
var controls = tab.getAttribute('aria-controls');

// Remove hidden attribute from tab panel to make it visible
document.getElementById(controls).removeAttribute('hidden');

// Set focus when required
if (setFocus) {
tab.focus();
};
};

// Deactivate all tabs and tab panels
function deactivateTabs () {
for (t = 0; t < tabs.length; t++) {
tabs[t].setAttribute('tabindex', '-1');
tabs[t].setAttribute('aria-selected', 'false');
};

for (p = 0; p < panels.length; p++) {
panels[p].setAttribute('hidden', 'hidden');
};
};

// Make a guess
function focusFirstTab () {
tabs[0].focus();
};

// Make a guess
function focusLastTab () {
tabs[tabs.length - 1].focus();
};

// Detect if a tab is deletable
function determineDeletable (event) {
target = event.target;

if (target.getAttribute('data-deletable') !== null) {
// Delete target tab
deleteTab(event, target);

// Update arrays related to tabs widget
generateArrays();

// Activate the first tab
activateTab(tabs[0]);
};
};

// Deletes a tab and its panel
function deleteTab (event) {
var target = event.target;
var panel = document.getElementById(target.getAttribute('aria-controls'));

target.parentElement.removeChild(target);
panel.parentElement.removeChild(panel);
};

// Determine whether there should be a delay
// when user navigates with the arrow keys
function determineDelay () {
var hasDelay = tablist.hasAttribute('data-delay');
var delay = 0;

if (hasDelay) {
var delayValue = tablist.getAttribute('data-delay');
if (delayValue) {
delay = delayValue;
}
else {
// If no value is specified, default to 300ms
delay = 300;
};
};

return delay;
};
})();
Loading

0 comments on commit 3d6f84e

Please sign in to comment.