mirror of https://github.com/istio/istio.io.git
Finish accessibility support in tab sets. (#3720)
This commit is contained in:
parent
ed310e73ff
commit
69830194de
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
|
@ -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 -}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
|
@ -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();
|
|
@ -131,3 +131,8 @@ function toggleAttribute(el, name) {
|
|||
el.setAttribute(name, "true");
|
||||
}
|
||||
}
|
||||
|
||||
function isPrintableCharacter(str) {
|
||||
return str.length === 1 && str.match(/\S/);
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,11 @@ header {
|
|||
@media (min-width: $bp-sm) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg.icon {
|
||||
fill: $textBrandColor;
|
||||
stroke: $textBrandColor;
|
||||
}
|
||||
}
|
||||
|
||||
#header-links {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue