Finish accessibility support in tab sets. (#3720)

This commit is contained in:
Martin Taillefer 2019-03-16 06:55:45 -07:00 committed by GitHub
parent ed310e73ff
commit 69830194de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 195 additions and 78 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,19 +7,19 @@
{{- /* We don't use the inner content, but Hugo needs this reference as a trigger to indicate this shortcode has a content area. */ -}}
{{- end -}}
<div id="{{ $tab_set_id }}" role="tablist">
<div id="{{ $tab_set_id }}" role="tablist" class="tabset">
<div class="tab-strip" data-cookie-name="{{ $cookie_name }}">
{{- range $i, $e := $tabs -}}
{{- $id := printf "%s-%d" $tab_set_id $i -}}
<button tabindex="-1" {{ if eq $i 0 }}class="active"{{ end }} data-cookie-value="{{ .cookie_value }}"
data-tab="{{ $id }}-panel" id="{{ $id }}-tab" role="tab"><span>{{ trim .name " " }}</span>
<button {{ if eq $i 0 }}aria-selected="true"{{ else }}tabindex="-1"{{ end }} data-cookie-value="{{ .cookie_value }}"
aria-controls="{{ $id }}-panel" id="{{ $id }}-tab" role="tab"><span>{{ trim .name " " }}</span>
</button>
{{- end -}}
</div>
<div class="tab-content">
{{- range $i, $e := $tabs -}}
{{- $id := printf "%s-%d" $tab_set_id $i -}}
<div{{ if eq $i 0 }} class="active"{{ end }} id="{{ $id }}-panel" role="tabpanel" aria-labelledby="{{ $id }}-tab">
<div{{ if ne $i 0 }} hidden{{ end }} id="{{ $id }}-panel" role="tabpanel" tabindex="0" aria-labelledby="{{ $id }}-tab">
{{- $text := partial "strip_indent.html" (dict "content" .content "pos" .Position) -}}
{{- $text := $text | markdownify }}
{{- $text | safeHTML -}}

View File

@ -5,6 +5,6 @@ mkdir -p generated/css generated/js generated/img
npx sass src/sass/_all.scss all.css -s compressed
mv all.css* generated/css
npx babel src/js/constants.js src/js/utils.js src/js/themes.js src/js/menu.js src/js/header.js src/js/sidebar.js src/js/tabs.js src/js/prism.js src/js/codeBlocks.js src/js/links.js src/js/scroll.js src/js/overlays.js src/js/lang.js --out-file generated/js/all.min.js --source-maps --minified --no-comments --presets minify
npx babel src/js/constants.js src/js/utils.js src/js/themes.js src/js/menu.js src/js/header.js src/js/sidebar.js src/js/tabset.js src/js/prism.js src/js/codeBlocks.js src/js/links.js src/js/scroll.js src/js/overlays.js src/js/lang.js --out-file generated/js/all.min.js --source-maps --minified --no-comments --presets minify
npx babel src/js/themes_init.js --out-file generated/js/themes_init.min.js --source-maps --minified --no-comments --presets minify
npx svgstore -o generated/img/icons.svg src/icons/**/*.svg

View File

@ -76,10 +76,6 @@ function handleMenu() {
}
}
function isPrintableCharacter(str) {
return str.length === 1 && str.match(/\S/);
}
listen(trigger, click, e => {
toggleOverlay(menu);
toggleAttribute(e.currentTarget, ariaExpanded);

View File

@ -1,53 +0,0 @@
"use strict";
function handleTabs() {
function updateLikeTabsets(cookieName, cookieValue) {
queryAll(document, "[role=tablist]").forEach(tabset => {
queryAll(tabset, ".tab-strip").forEach(strip => {
if (strip.dataset.cookieName === cookieName) {
queryAll(strip, button).forEach(tab => {
if (tab.dataset.cookieValue === cookieValue) {
tab.classList.add(active);
getById(tab.dataset.tab).classList.add(active);
} else {
tab.classList.remove(active);
getById(tab.dataset.tab).classList.remove(active);
}
});
}
});
});
}
queryAll(document, "[role=tablist]").forEach(tabset => {
queryAll(tabset, ".tab-strip").forEach(strip => {
const cookieName = strip.dataset.cookieName;
if (cookieName) {
const cookieValue = readCookie(cookieName);
if (cookieValue) {
updateLikeTabsets(cookieName, cookieValue);
}
}
// attach the event handlers to support tab sets
queryAll(strip, button).forEach(tab => {
listen(tab, click, () => {
queryAll(strip, button).forEach(tab2 => {
tab2.classList.remove(active);
getById(tab2.dataset.tab).classList.remove(active);
});
tab.classList.add(active);
getById(tab.dataset.tab).classList.add(active);
if (cookieName !== null) {
createCookie(cookieName, tab.dataset.cookieValue);
updateLikeTabsets(cookieName, tab.dataset.cookieValue);
}
});
});
});
});
}
handleTabs();

172
src/js/tabset.js Normal file
View File

@ -0,0 +1,172 @@
"use strict";
function handleTabs() {
function updateLikeTabsets(cookieName, cookieValue) {
queryAll(document, ".tabset").forEach(tabset => {
queryAll(tabset, ".tab-strip").forEach(strip => {
if (strip.dataset.cookieName === cookieName) {
queryAll(strip, "[role=tab]").forEach(tab => {
const panel = getById(tab.getAttribute("aria-controls"));
if (tab.dataset.cookieValue === cookieValue) {
tab.setAttribute("aria-selected", "true");
tab.setAttribute("tabindex", "-1");
panel.removeAttribute("hidden");
} else {
tab.removeAttribute("aria-selected");
tab.setAttribute("tabindex", "-1");
panel.setAttribute("hidden", "");
}
});
}
});
});
}
queryAll(document, ".tabset").forEach(tabset => {
const strip = query(tabset, ".tab-strip");
if (strip === null) {
return;
}
const cookieName = strip.dataset.cookieName;
const panels = queryAll(tabset, '[role=tabpanel]');
const tabs = [];
queryAll(strip, '[role=tab]').forEach(tab => {
tabs.push(tab);
});
function activateTab(tab) {
deactivateAllTabs();
tab.removeAttribute('tabindex');
tab.setAttribute('aria-selected', 'true');
getById(tab.getAttribute('aria-controls')).removeAttribute('hidden');
}
function deactivateAllTabs() {
tabs.forEach(tab => {
tab.setAttribute('tabindex', '-1');
tab.setAttribute('aria-selected', 'false');
});
panels.forEach(panel => {
panel.setAttribute('hidden', '');
});
}
function focusFirstTab() {
tabs[0].focus();
}
function focusLastTab() {
tabs[tabs.length - 1].focus();
}
function focusNextTab(current) {
const index = tabs.indexOf(current);
if (index < tabs.length - 1) {
tabs[index+1].focus();
} else {
tabs[0].focus();
}
}
function focusPrevTab(current) {
const index = tabs.indexOf(current);
if (index > 0) {
tabs[index-1].focus();
} else {
tabs[tabs.length - 1].focus();
}
}
function getIndexFirstChars(startIndex, ch) {
for (let i = startIndex; i < tabs.length; i++) {
const firstChar = tabs[i].textContent.trim().substring(0, 1).toLowerCase();
if (ch === firstChar) {
return i;
}
}
return -1;
}
function focusTabByChar(current, ch) {
ch = ch.toLowerCase();
// Check remaining slots in the strip
let index = getIndexFirstChars(tabs.indexOf(current) + 1, ch);
// If not found in remaining slots, check from beginning
if (index === -1) {
index = getIndexFirstChars(0, ch);
}
// If match was found...
if (index > -1) {
tabs[index].focus();
}
}
if (cookieName) {
const cookieValue = readCookie(cookieName);
if (cookieValue) {
updateLikeTabsets(cookieName, cookieValue);
}
}
// attach the event handlers to support tab sets
queryAll(strip, button).forEach(tab => {
listen(tab, "focus", () => {
activateTab(tab);
if (cookieName) {
createCookie(cookieName, tab.dataset.cookieValue);
updateLikeTabsets(cookieName, tab.dataset.cookieValue);
}
});
listen(tab, keydown, e => {
const ch = e.key;
if (e.ctrlKey || e.altKey || e.metaKey) {
// nothing
}
else if (e.shiftKey) {
if (isPrintableCharacter(ch)) {
focusTabByChar(tab, ch);
}
} else {
switch (e.keyCode) {
case keyCodes.LEFT:
focusPrevTab(tab);
break;
case keyCodes.RIGHT:
focusNextTab(tab);
break;
case keyCodes.HOME:
focusFirstTab();
break;
case keyCodes.END:
focusLastTab();
break;
default:
if (isPrintableCharacter(ch)) {
focusTabByChar(tab, ch);
}
break;
}
e.preventDefault();
e.cancelBubble = true;
}
});
});
});
}
handleTabs();

View File

@ -131,3 +131,8 @@ function toggleAttribute(el, name) {
el.setAttribute(name, "true");
}
}
function isPrintableCharacter(str) {
return str.length === 1 && str.match(/\S/);
}

View File

@ -147,6 +147,11 @@ header {
@media (min-width: $bp-sm) {
display: none;
}
svg.icon {
fill: $textBrandColor;
stroke: $textBrandColor;
}
}
#header-links {

View File

@ -1,4 +1,4 @@
[role=tablist] {
.tabset {
padding: 0;
margin: 1em 0 1em 1em;
@ -35,7 +35,7 @@
}
}
&.active {
&[aria-selected=true] {
background-color: $tabsetSelectedTabBackgroundColor;
cursor: default;
box-shadow: none;
@ -60,13 +60,5 @@
p:last-of-type {
margin-bottom: 0
}
> div {
display: none;
&.active {
display: block;
}
}
}
}