diff --git a/README.md b/README.md index 92a160c..83515c2 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ __Conventions__ Then, follow the conventions given in this minimal example (in bold). - use classes needed (```js-tabs```, ```js-tablist```, ```js-tablist__item```, ```js-tablist__link```, ```js-tabcontent```) -- be careful to use the convention in the links ```a href="#id_first" id="label_id_first"``` +- be careful to use the convention in the links ```a href="#id_first"``` - check that your anchors are working. - for accessibility purposes (for VoiceOver), the plugin has to give focus to ```hx``` (```h2```, ```h3```, ```h4```, etc.) in tab contents. @@ -51,10 +51,10 @@ Example without ```hx```:
@@ -70,10 +70,10 @@ Example with ```hx```:
@@ -87,6 +87,10 @@ Example with ```hx```:
``` +The script is launched when the page is loaded. If you need to execute it on AJAX-inserted content, you may use for example on `
your tab panel source
`: + +```van11yAccessibleTabPanelAria(document.getElementById('newContent'));``` + ## Keyboard shortcuts If you focus on the tabs “buttons”: diff --git a/bower.json b/bower.json index 2760dad..31ec73b 100644 --- a/bower.json +++ b/bower.json @@ -3,7 +3,7 @@ "description": "ES2015 accessible tabs panel system, using ARIA (compatible IE9+ when transpiled)", "homepage": "https://van11y.net/accessible-tab-panel/", "main": "van11y-accessible-tab-panel-aria.es6.js", - "version": "1.1.0", + "version": "2.0.0", "keywords": [ "Accessibility", "ARIA", diff --git a/demo/index-fr.html b/demo/index-fr.html index 0420a93..8ac3541 100644 --- a/demo/index-fr.html +++ b/demo/index-fr.html @@ -45,13 +45,13 @@

Démos : Système d’onglets accessibles avec
@@ -73,13 +73,13 @@

Un autre titre

@@ -102,6 +102,6 @@

Un autre titre

- + diff --git a/demo/index.html b/demo/index.html index 9162a6f..1c6554b 100644 --- a/demo/index.html +++ b/demo/index.html @@ -3,7 +3,7 @@ Demo: Accessible tab panel using ARIA and Vanilla Javascript - Van11y - + @@ -44,13 +44,13 @@

Demos: Accessible tab panel using
@@ -72,13 +72,13 @@

Another title

@@ -88,15 +88,15 @@

Title

Other title

here the content of 2nd tab
- Displayed by default thanks to data-selected="1" attribute on js-tablist__link. + Displayed by default thanks to data-selected="1" attribute on js-tablist__link.

Another title

here the content of 3rd tab
- - + +
diff --git a/dist/van11y-accessible-tab-panel-aria.es6.js b/dist/van11y-accessible-tab-panel-aria.es6.js index c6f3ece..70cfd4d 100644 --- a/dist/van11y-accessible-tab-panel-aria.es6.js +++ b/dist/van11y-accessible-tab-panel-aria.es6.js @@ -1,488 +1,491 @@ -/* - * ES2015 accessible tabs panel system, using ARIA - * Website: https://van11y.net/accessible-tab-panel/ - * License MIT: https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE - */ -(doc => { - - 'use strict'; - - const TABS_JS = 'js-tabs'; - const TABS_JS_LIST = 'js-tablist'; - const TABS_JS_LISTITEM = 'js-tablist__item'; - const TABS_JS_LISTLINK = 'js-tablist__link'; - const TABS_JS_CONTENT = 'js-tabcontent'; - const TABS_JS_LINK_TO_TAB = 'js-link-to-tab'; - - const TABS_DATA_PREFIX_CLASS = 'data-tabs-prefix-class'; - const TABS_DATA_HX = 'data-hx'; - const TABS_DATA_GENERATED_HX_CLASS = 'data-tabs-generated-hx-class'; - const TABS_DATA_EXISTING_HX = 'data-existing-hx'; - - const TABS_DATA_SELECTED_TAB = 'data-selected'; - - const TABS_PREFIX_IDS = 'label_'; - - const TABS_STYLE = 'tabs'; - const TABS_LIST_STYLE = 'tabs__list'; - const TABS_LISTITEM_STYLE = 'tabs__item'; - const TABS_LINK_STYLE = 'tabs__link'; - const TABS_CONTENT_STYLE = 'tabs__content'; - - const TABS_HX_DEFAULT_CLASS = 'invisible'; - - const TABS_ROLE_TABLIST = 'tablist'; - const TABS_ROLE_TAB = 'tab'; - const TABS_ROLE_TABPANEL = 'tabpanel'; - const TABS_ROLE_PRESENTATION = 'presentation'; - - const ATTR_ROLE = 'role'; - const ATTR_LABELLEDBY = 'aria-labelledby'; - const ATTR_HIDDEN = 'aria-hidden'; - const ATTR_CONTROLS = 'aria-controls'; - const ATTR_SELECTED = 'aria-selected'; - - const DELAY_HASH_UPDATE = 1000; - - let hash = window.location.hash.replace('#', ''); - - //const IS_OPENED_CLASS = 'is-opened'; - - - - const findById = id => doc.getElementById(id); - - const addClass = (el, className) => { - if (el.classList) { - el.classList.add(className); // IE 10+ - } else { - el.className += ' ' + className; // IE 8+ - } - } - - /*const removeClass = (el, className) => { - if (el.classList) { - el.classList.remove(className); // IE 10+ - } - else { - el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ - } - }*/ - - const hasClass = (el, className) => { - if (el.classList) { - return el.classList.contains(className); // IE 10+ - } else { - return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? - } - } - - const setAttributes = (node, attrs) => { - Object - .keys(attrs) - .forEach((attribute) => { - node.setAttribute(attribute, attrs[attribute]); - }); - }; - /* const triggerEvent = (el, event_type) => { - if (el.fireEvent) { - el.fireEvent('on' + event_type); - } - else { - let evObj = document.createEvent('Events'); - evObj.initEvent(event_type, true, false); - el.dispatchEvent(evObj); - } - }*/ - const unSelectLinks = (elts) => { - elts - .forEach((link_node) => { - setAttributes(link_node, { - [ATTR_SELECTED]: 'false', - 'tabindex': '-1' - }); - }); - } - const unSelectContents = (elts) => { - elts - .forEach((content_node) => { - content_node.setAttribute(ATTR_HIDDEN, true); - }); - } - - const selectLink = (el) => { - let destination = findById(el.getAttribute(ATTR_CONTROLS)); - setAttributes(el, { - [ATTR_SELECTED]: 'true', - 'tabindex': '0' - }); - destination.removeAttribute(ATTR_HIDDEN); - setTimeout(function() { - el.focus(); - }, 0); - setTimeout(function() { - history.pushState(null, null, location.pathname + location.search + '#' + el.getAttribute(ATTR_CONTROLS)) - }, DELAY_HASH_UPDATE); - } - - const selectLinkInList = (itemsList, linkList, contentList, param) => { - let indice_trouve; - - itemsList - .forEach((itemNode, index) => { - if (itemNode.querySelector('.' + TABS_JS_LISTLINK).getAttribute(ATTR_SELECTED) === 'true') { - indice_trouve = index; - } - }); - unSelectLinks(linkList); - unSelectContents(contentList); - if (param === 'next') { - selectLink(linkList[indice_trouve + 1]); - setTimeout(function() { - linkList[indice_trouve + 1].focus(); - }, 0); - } - if (param === 'prev') { - selectLink(linkList[indice_trouve - 1]); - setTimeout(function() { - linkList[indice_trouve - 1].focus(); - }, 0); - } - - } - - /* gets an element el, search if it is child of parent class, returns id of the parent */ - let searchParent = (el, parentClass) => { - let found = false; - let parentElement = el.parentNode; - while (parentElement && found === false) { - if (hasClass(parentElement, parentClass) === true) { - found = true; - } else { - parentElement = parentElement.parentNode; - } - } - if (found === true) { - return parentElement.getAttribute('id'); - } else { - return ''; - } - } - - - // Find all TABS - const $listTabs = () => [].slice.call(doc.querySelectorAll('.' + TABS_JS)); - - - const onLoad = () => { - - $listTabs() - .forEach((tabs_node, index) => { - - let iLisible = index + 1; - let prefixClassName = tabs_node.hasAttribute(TABS_DATA_PREFIX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_PREFIX_CLASS) + '-' : ''; - let hx = tabs_node.hasAttribute(TABS_DATA_HX) === true ? tabs_node.getAttribute(TABS_DATA_HX) : ''; - let hxGeneratedClass = tabs_node.hasAttribute(TABS_DATA_GENERATED_HX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_GENERATED_HX_CLASS) : TABS_HX_DEFAULT_CLASS; - let existingHx = tabs_node.hasAttribute(TABS_DATA_EXISTING_HX) === true ? tabs_node.getAttribute(TABS_DATA_EXISTING_HX) : ''; - let $tabList = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LIST)); - let $tabListItems = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $tabListLinks = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $tabListPanels = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_CONTENT)); - let noTabSelected = true; - - // container - addClass(tabs_node, prefixClassName + TABS_STYLE); - tabs_node.setAttribute('id', TABS_STYLE + iLisible); - - // ul - $tabList.forEach((tabList) => { - addClass(tabList, prefixClassName + TABS_LIST_STYLE); - setAttributes(tabList, { - [ATTR_ROLE]: TABS_ROLE_TABLIST, - 'id': TABS_LIST_STYLE + iLisible - }); - }); - // li - $tabListItems.forEach((tabListItem, index) => { - addClass(tabListItem, prefixClassName + TABS_LISTITEM_STYLE); - setAttributes(tabListItem, { - [ATTR_ROLE]: TABS_ROLE_PRESENTATION, - 'id': TABS_LISTITEM_STYLE + iLisible + '-' + (index + 1) - }); - }); - // a - $tabListLinks.forEach((tabListLink) => { - let idHref = tabListLink.getAttribute("href").replace('#', ''); - let panelControlled = findById(idHref); - let linkText = tabListLink.innerText; - let panelSelected = tabListLink.hasAttribute(TABS_DATA_SELECTED_TAB) === true; - - addClass(tabListLink, prefixClassName + TABS_LINK_STYLE); - setAttributes(tabListLink, { - 'id' : TABS_PREFIX_IDS + idHref, - [ATTR_ROLE]: TABS_ROLE_TAB, - [ATTR_CONTROLS]: idHref, - 'tabindex': '-1', - [ATTR_SELECTED]: 'false' - }); - - // panel controlled - setAttributes(panelControlled, { - [ATTR_HIDDEN]: 'true', - [ATTR_ROLE]: TABS_ROLE_TABPANEL, - [ATTR_LABELLEDBY]: TABS_PREFIX_IDS + idHref - }); - addClass(panelControlled, prefixClassName + TABS_CONTENT_STYLE); - - // if already selected - if (panelSelected && noTabSelected) { - noTabSelected = false; - setAttributes(tabListLink, { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - setAttributes(panelControlled, { - [ATTR_HIDDEN]: 'false' - }); - } - - // hx - if (hx !== '') { - let hx_node = document.createElement(hx); - hx_node.setAttribute('class', hxGeneratedClass); - hx_node.setAttribute('tabindex', '0'); - hx_node.innerHTML = linkText; - panelControlled.insertBefore(hx_node, panelControlled.firstChild); - } - // existingHx - - if (existingHx !== '') { - let $hx_existing = [].slice.call(panelControlled.querySelectorAll(existingHx + ':first-child')); - $hx_existing.forEach((hx_item) => { - hx_item.setAttribute('tabindex', '0'); - }); - - } - - tabListLink.removeAttribute('href'); - - }); - - - if (hash !== '') { - let nodeHashed = findById(hash); - if (nodeHashed !== null) { // just in case of an dumb error - // search if hash is current tabs_node - if (tabs_node.querySelector('#' + hash) !== null) { - // search if hash is ON tabs - if (hasClass(nodeHashed, TABS_JS_CONTENT) === true) { - // unselect others - unSelectLinks($tabListLinks); - unSelectContents($tabListPanels); - // select this one - nodeHashed.removeAttribute(ATTR_HIDDEN); - let linkHashed = findById(TABS_PREFIX_IDS + hash); - setAttributes(linkHashed, { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - noTabSelected = false; - } else { - // search if hash is IN tabs - let panelParentId = searchParent(nodeHashed, TABS_JS_CONTENT); - if (panelParentId !== '') { - // unselect others - unSelectLinks($tabListLinks); - unSelectContents($tabListPanels); - // select this one - let panelParent = findById(panelParentId); - panelParent.removeAttribute(ATTR_HIDDEN); - let linkParent = findById(TABS_PREFIX_IDS + panelParentId); - setAttributes(linkParent, { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - noTabSelected = false; - } - } - } - } - } - - // if no selected => select first - if (noTabSelected === true) { - setAttributes($tabListLinks[0], { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - let panelFirst = findById($tabListLinks[0].getAttribute(ATTR_CONTROLS)); - panelFirst.removeAttribute(ATTR_HIDDEN); - } - - }); - - // click on - ['click', 'keydown'] - .forEach(eventName => { - //let isCtrl = false; - - doc.body - .addEventListener(eventName, e => { - - // click on a tab link or on something IN a tab link - let parentLink = searchParent(e.target, TABS_JS_LISTLINK); - if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'click') { - let linkSelected = hasClass(e.target, TABS_JS_LISTLINK) === true ? e.target : findById(parentLink); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - - // aria selected false on all links - unSelectLinks($parentListLinks); - // add aria-hidden on all tabs contents - unSelectContents($parentListContents); - // add aria selected on selected link + show linked panel - selectLink(linkSelected); - - e.preventDefault(); - } - - // Key down on tabs - if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'keydown') { - //let linkSelected = hasClass( e.target, TABS_JS_LISTLINK) === true ? e.target : findById( parentLink ); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); - let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); - - // strike home on a tab => 1st tab - if (e.keyCode === 36) { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - e.preventDefault(); - } - // strike end on a tab => last tab - else if (e.keyCode === 35) { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - e.preventDefault(); - } - // strike up or left on the tab => previous tab - else if ((e.keyCode === 37 || e.keyCode === 38) && !e.ctrlKey) { - if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - e.preventDefault(); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); - e.preventDefault(); - } - } - // strike down or right in the tab => next tab - else if ((e.keyCode === 40 || e.keyCode === 39) && !e.ctrlKey) { - if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - e.preventDefault(); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); - e.preventDefault(); - } - } - - } - - // Key down in tab panels - let parentTabPanelId = searchParent(e.target, TABS_JS_CONTENT); - if (parentTabPanelId !== '' && eventName === 'keydown') { - let linkSelected = findById(findById(parentTabPanelId).getAttribute(ATTR_LABELLEDBY)); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); - let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); - - - // strike up + ctrl => go to header - if (e.keyCode === 38 && e.ctrlKey) { - setTimeout(function() { - linkSelected.focus(); - }, 0); - e.preventDefault(); - } - // strike pageup + ctrl => go to prev header - if (e.keyCode === 33 && e.ctrlKey) { - // go to header - linkSelected.focus(); - e.preventDefault(); - // then previous - if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); - } - - } - // strike pagedown + ctrl => go to next header - if (e.keyCode === 34 && e.ctrlKey) { - // go to header - linkSelected.focus(); - e.preventDefault(); - // then next - if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); - } - } - } - - // click on a tab link - let parentLinkToPanelId = searchParent(e.target, TABS_JS_LINK_TO_TAB); - if ((hasClass(e.target, TABS_JS_LINK_TO_TAB) === true || parentLinkToPanelId !== '') && eventName === 'click') { - let panelSelectedId = hasClass(e.target, TABS_JS_LINK_TO_TAB) === true ? e.target.getAttribute('href').replace('#', '') : findById(parentLinkToPanelId).replace('#', ''); - let panelSelected = findById(panelSelectedId); - let buttonPanelSelected = findById(panelSelected.getAttribute(ATTR_LABELLEDBY)); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(buttonPanelSelected); - - e.preventDefault(); - } - - }, true); - }); - document.removeEventListener('DOMContentLoaded', onLoad); - } - - document.addEventListener('DOMContentLoaded', onLoad); - - +/* + * ES2015 accessible tabs panel system, using ARIA + * Website: https://van11y.net/accessible-tab-panel/ + * License MIT: https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE + */ +(doc => { + + 'use strict'; + + const TABS_JS = 'js-tabs'; + const TABS_JS_LIST = 'js-tablist'; + const TABS_JS_LISTITEM = 'js-tablist__item'; + const TABS_JS_LISTLINK = 'js-tablist__link'; + const TABS_JS_CONTENT = 'js-tabcontent'; + const TABS_JS_LINK_TO_TAB = 'js-link-to-tab'; + + const TABS_DATA_PREFIX_CLASS = 'data-tabs-prefix-class'; + const TABS_DATA_HX = 'data-hx'; + const TABS_DATA_GENERATED_HX_CLASS = 'data-tabs-generated-hx-class'; + const TABS_DATA_EXISTING_HX = 'data-existing-hx'; + + const TABS_DATA_SELECTED_TAB = 'data-selected'; + + const TABS_PREFIX_IDS = 'label_'; + + const TABS_STYLE = 'tabs'; + const TABS_LIST_STYLE = 'tabs__list'; + const TABS_LISTITEM_STYLE = 'tabs__item'; + const TABS_LINK_STYLE = 'tabs__link'; + const TABS_CONTENT_STYLE = 'tabs__content'; + + const TABS_HX_DEFAULT_CLASS = 'invisible'; + + const TABS_ROLE_TABLIST = 'tablist'; + const TABS_ROLE_TAB = 'tab'; + const TABS_ROLE_TABPANEL = 'tabpanel'; + const TABS_ROLE_PRESENTATION = 'presentation'; + + const ATTR_ROLE = 'role'; + const ATTR_LABELLEDBY = 'aria-labelledby'; + const ATTR_HIDDEN = 'aria-hidden'; + const ATTR_CONTROLS = 'aria-controls'; + const ATTR_SELECTED = 'aria-selected'; + + const DELAY_HASH_UPDATE = 1000; + + let hash = window.location.hash.replace('#', ''); + + //const IS_OPENED_CLASS = 'is-opened'; + + + + const findById = id => doc.getElementById(id); + + const addClass = (el, className) => { + if (el.classList) { + el.classList.add(className); // IE 10+ + } else { + el.className += ' ' + className; // IE 8+ + } + } + + /*const removeClass = (el, className) => { + if (el.classList) { + el.classList.remove(className); // IE 10+ + } + else { + el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ + } + }*/ + + const hasClass = (el, className) => { + if (el.classList) { + return el.classList.contains(className); // IE 10+ + } else { + return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? + } + } + + const setAttributes = (node, attrs) => { + Object + .keys(attrs) + .forEach((attribute) => { + node.setAttribute(attribute, attrs[attribute]); + }); + }; + const unSelectLinks = (elts) => { + elts + .forEach((link_node) => { + setAttributes(link_node, { + [ATTR_SELECTED]: 'false', + 'tabindex': '-1' + }); + }); + } + const unSelectContents = (elts) => { + elts + .forEach((content_node) => { + content_node.setAttribute(ATTR_HIDDEN, true); + }); + } + + const selectLink = (el) => { + let destination = findById(el.getAttribute(ATTR_CONTROLS)); + setAttributes(el, { + [ATTR_SELECTED]: 'true', + 'tabindex': '0' + }); + destination.removeAttribute(ATTR_HIDDEN); + setTimeout(function() { + el.focus(); + }, 0); + setTimeout(function() { + history.pushState(null, null, location.pathname + location.search + '#' + el.getAttribute(ATTR_CONTROLS)) + }, DELAY_HASH_UPDATE); + } + + const selectLinkInList = (itemsList, linkList, contentList, param) => { + let indice_trouve; + + itemsList + .forEach((itemNode, index) => { + if (itemNode.querySelector('.' + TABS_JS_LISTLINK).getAttribute(ATTR_SELECTED) === 'true') { + indice_trouve = index; + } + }); + unSelectLinks(linkList); + unSelectContents(contentList); + if (param === 'next') { + selectLink(linkList[indice_trouve + 1]); + setTimeout(function() { + linkList[indice_trouve + 1].focus(); + }, 0); + } + if (param === 'prev') { + selectLink(linkList[indice_trouve - 1]); + setTimeout(function() { + linkList[indice_trouve - 1].focus(); + }, 0); + } + + } + + /* gets an element el, search if it is child of parent class, returns id of the parent */ + let searchParent = (el, parentClass) => { + let found = false; + let parentElement = el.parentNode; + while (parentElement && found === false) { + if (hasClass(parentElement, parentClass) === true) { + found = true; + } else { + parentElement = parentElement.parentNode; + } + } + if (found === true) { + return parentElement.getAttribute('id'); + } else { + return ''; + } + } + + + /** Find all tabs inside a container + * @param {Node} node Default document + * @return {Array} + */ + const $listTabs = (node = doc) => [].slice.call(node.querySelectorAll('.' + TABS_JS)); + + + /** + * Build tooltips for a container + * @param {Node} node + */ + const attach = (node) => { + + $listTabs(node) + .forEach((tabs_node) => { + + let iLisible = Math.random().toString(32).slice(2, 12); + let prefixClassName = tabs_node.hasAttribute(TABS_DATA_PREFIX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_PREFIX_CLASS) + '-' : ''; + let hx = tabs_node.hasAttribute(TABS_DATA_HX) === true ? tabs_node.getAttribute(TABS_DATA_HX) : ''; + let hxGeneratedClass = tabs_node.hasAttribute(TABS_DATA_GENERATED_HX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_GENERATED_HX_CLASS) : TABS_HX_DEFAULT_CLASS; + let existingHx = tabs_node.hasAttribute(TABS_DATA_EXISTING_HX) === true ? tabs_node.getAttribute(TABS_DATA_EXISTING_HX) : ''; + let $tabList = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LIST)); + let $tabListItems = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $tabListLinks = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $tabListPanels = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_CONTENT)); + let noTabSelected = true; + + // container + addClass(tabs_node, prefixClassName + TABS_STYLE); + tabs_node.setAttribute('id', TABS_STYLE + iLisible); + + // ul + $tabList.forEach((tabList) => { + addClass(tabList, prefixClassName + TABS_LIST_STYLE); + setAttributes(tabList, { + [ATTR_ROLE]: TABS_ROLE_TABLIST, + 'id': TABS_LIST_STYLE + iLisible + }); + }); + // li + $tabListItems.forEach((tabListItem, index) => { + addClass(tabListItem, prefixClassName + TABS_LISTITEM_STYLE); + setAttributes(tabListItem, { + [ATTR_ROLE]: TABS_ROLE_PRESENTATION, + 'id': TABS_LISTITEM_STYLE + iLisible + '-' + (index + 1) + }); + }); + // a + $tabListLinks.forEach((tabListLink) => { + let idHref = tabListLink.getAttribute("href").replace('#', ''); + let panelControlled = findById(idHref); + let linkText = tabListLink.innerText; + let panelSelected = tabListLink.hasAttribute(TABS_DATA_SELECTED_TAB) === true; + + addClass(tabListLink, prefixClassName + TABS_LINK_STYLE); + setAttributes(tabListLink, { + 'id': TABS_PREFIX_IDS + idHref, + [ATTR_ROLE]: TABS_ROLE_TAB, + [ATTR_CONTROLS]: idHref, + 'tabindex': '-1', + [ATTR_SELECTED]: 'false' + }); + + // panel controlled + setAttributes(panelControlled, { + [ATTR_HIDDEN]: 'true', + [ATTR_ROLE]: TABS_ROLE_TABPANEL, + [ATTR_LABELLEDBY]: TABS_PREFIX_IDS + idHref + }); + addClass(panelControlled, prefixClassName + TABS_CONTENT_STYLE); + + // if already selected + if (panelSelected && noTabSelected) { + noTabSelected = false; + setAttributes(tabListLink, { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + setAttributes(panelControlled, { + [ATTR_HIDDEN]: 'false' + }); + } + + // hx + if (hx !== '') { + let hx_node = document.createElement(hx); + hx_node.setAttribute('class', hxGeneratedClass); + hx_node.setAttribute('tabindex', '0'); + hx_node.innerHTML = linkText; + panelControlled.insertBefore(hx_node, panelControlled.firstChild); + } + // existingHx + + if (existingHx !== '') { + let $hx_existing = [].slice.call(panelControlled.querySelectorAll(existingHx + ':first-child')); + $hx_existing.forEach((hx_item) => { + hx_item.setAttribute('tabindex', '0'); + }); + + } + + tabListLink.removeAttribute('href'); + + }); + + + if (hash !== '') { + let nodeHashed = findById(hash); + if (nodeHashed !== null) { // just in case of an dumb error + // search if hash is current tabs_node + if (tabs_node.querySelector('#' + hash) !== null) { + // search if hash is ON tabs + if (hasClass(nodeHashed, TABS_JS_CONTENT) === true) { + // unselect others + unSelectLinks($tabListLinks); + unSelectContents($tabListPanels); + // select this one + nodeHashed.removeAttribute(ATTR_HIDDEN); + let linkHashed = findById(TABS_PREFIX_IDS + hash); + setAttributes(linkHashed, { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + noTabSelected = false; + } else { + // search if hash is IN tabs + let panelParentId = searchParent(nodeHashed, TABS_JS_CONTENT); + if (panelParentId !== '') { + // unselect others + unSelectLinks($tabListLinks); + unSelectContents($tabListPanels); + // select this one + let panelParent = findById(panelParentId); + panelParent.removeAttribute(ATTR_HIDDEN); + let linkParent = findById(TABS_PREFIX_IDS + panelParentId); + setAttributes(linkParent, { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + noTabSelected = false; + } + } + } + } + } + + // if no selected => select first + if (noTabSelected === true) { + setAttributes($tabListLinks[0], { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + let panelFirst = findById($tabListLinks[0].getAttribute(ATTR_CONTROLS)); + panelFirst.removeAttribute(ATTR_HIDDEN); + } + + }); + }; + + /* listeners */ + ['click', 'keydown'] + .forEach(eventName => { + //let isCtrl = false; + + doc.body + .addEventListener(eventName, e => { + + // click on a tab link or on something IN a tab link + let parentLink = searchParent(e.target, TABS_JS_LISTLINK); + if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'click') { + let linkSelected = hasClass(e.target, TABS_JS_LISTLINK) === true ? e.target : findById(parentLink); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + + // aria selected false on all links + unSelectLinks($parentListLinks); + // add aria-hidden on all tabs contents + unSelectContents($parentListContents); + // add aria selected on selected link + show linked panel + selectLink(linkSelected); + + e.preventDefault(); + } + + // Key down on tabs + if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'keydown') { + //let linkSelected = hasClass( e.target, TABS_JS_LISTLINK) === true ? e.target : findById( parentLink ); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); + let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); + + // strike home on a tab => 1st tab + if (e.keyCode === 36) { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + e.preventDefault(); + } + // strike end on a tab => last tab + else if (e.keyCode === 35) { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + e.preventDefault(); + } + // strike up or left on the tab => previous tab + else if ((e.keyCode === 37 || e.keyCode === 38) && !e.ctrlKey) { + if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + e.preventDefault(); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); + e.preventDefault(); + } + } + // strike down or right in the tab => next tab + else if ((e.keyCode === 40 || e.keyCode === 39) && !e.ctrlKey) { + if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + e.preventDefault(); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); + e.preventDefault(); + } + } + + } + + // Key down in tab panels + let parentTabPanelId = searchParent(e.target, TABS_JS_CONTENT); + if (parentTabPanelId !== '' && eventName === 'keydown') { + let linkSelected = findById(findById(parentTabPanelId).getAttribute(ATTR_LABELLEDBY)); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); + let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); + + + // strike up + ctrl => go to header + if (e.keyCode === 38 && e.ctrlKey) { + setTimeout(function() { + linkSelected.focus(); + }, 0); + e.preventDefault(); + } + // strike pageup + ctrl => go to prev header + if (e.keyCode === 33 && e.ctrlKey) { + // go to header + linkSelected.focus(); + e.preventDefault(); + // then previous + if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); + } + + } + // strike pagedown + ctrl => go to next header + if (e.keyCode === 34 && e.ctrlKey) { + // go to header + linkSelected.focus(); + e.preventDefault(); + // then next + if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); + } + } + } + + // click on a tab link + let parentLinkToPanelId = searchParent(e.target, TABS_JS_LINK_TO_TAB); + if ((hasClass(e.target, TABS_JS_LINK_TO_TAB) === true || parentLinkToPanelId !== '') && eventName === 'click') { + let panelSelectedId = hasClass(e.target, TABS_JS_LINK_TO_TAB) === true ? e.target.getAttribute('href').replace('#', '') : findById(parentLinkToPanelId).replace('#', ''); + let panelSelected = findById(panelSelectedId); + let buttonPanelSelected = findById(panelSelected.getAttribute(ATTR_LABELLEDBY)); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(buttonPanelSelected); + + e.preventDefault(); + } + + }, true); + }); + + const onLoad = () => { + attach(); + document.removeEventListener('DOMContentLoaded', onLoad); + } + + document.addEventListener('DOMContentLoaded', onLoad); + + window.van11yAccessibleTabPanelAria = attach; + + })(document); \ No newline at end of file diff --git a/dist/van11y-accessible-tab-panel-aria.js b/dist/van11y-accessible-tab-panel-aria.js index e12fa96..3ac4ae7 100644 --- a/dist/van11y-accessible-tab-panel-aria.js +++ b/dist/van11y-accessible-tab-panel-aria.js @@ -1,458 +1,462 @@ -/* - * ES2015 accessible tabs panel system, using ARIA - * Website: https://van11y.net/accessible-tab-panel/ - * License MIT: https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE - */ -'use strict'; - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -(function (doc) { - - 'use strict'; - - var TABS_JS = 'js-tabs'; - var TABS_JS_LIST = 'js-tablist'; - var TABS_JS_LISTITEM = 'js-tablist__item'; - var TABS_JS_LISTLINK = 'js-tablist__link'; - var TABS_JS_CONTENT = 'js-tabcontent'; - var TABS_JS_LINK_TO_TAB = 'js-link-to-tab'; - - var TABS_DATA_PREFIX_CLASS = 'data-tabs-prefix-class'; - var TABS_DATA_HX = 'data-hx'; - var TABS_DATA_GENERATED_HX_CLASS = 'data-tabs-generated-hx-class'; - var TABS_DATA_EXISTING_HX = 'data-existing-hx'; - - var TABS_DATA_SELECTED_TAB = 'data-selected'; - - var TABS_PREFIX_IDS = 'label_'; - - var TABS_STYLE = 'tabs'; - var TABS_LIST_STYLE = 'tabs__list'; - var TABS_LISTITEM_STYLE = 'tabs__item'; - var TABS_LINK_STYLE = 'tabs__link'; - var TABS_CONTENT_STYLE = 'tabs__content'; - - var TABS_HX_DEFAULT_CLASS = 'invisible'; - - var TABS_ROLE_TABLIST = 'tablist'; - var TABS_ROLE_TAB = 'tab'; - var TABS_ROLE_TABPANEL = 'tabpanel'; - var TABS_ROLE_PRESENTATION = 'presentation'; - - var ATTR_ROLE = 'role'; - var ATTR_LABELLEDBY = 'aria-labelledby'; - var ATTR_HIDDEN = 'aria-hidden'; - var ATTR_CONTROLS = 'aria-controls'; - var ATTR_SELECTED = 'aria-selected'; - - var DELAY_HASH_UPDATE = 1000; - - var hash = window.location.hash.replace('#', ''); - - //const IS_OPENED_CLASS = 'is-opened'; - - var findById = function findById(id) { - return doc.getElementById(id); - }; - - var addClass = function addClass(el, className) { - if (el.classList) { - el.classList.add(className); // IE 10+ - } else { - el.className += ' ' + className; // IE 8+ - } - }; - - /*const removeClass = (el, className) => { - if (el.classList) { - el.classList.remove(className); // IE 10+ - } - else { - el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ - } - }*/ - - var hasClass = function hasClass(el, className) { - if (el.classList) { - return el.classList.contains(className); // IE 10+ - } else { - return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? - } - }; - - var setAttributes = function setAttributes(node, attrs) { - Object.keys(attrs).forEach(function (attribute) { - node.setAttribute(attribute, attrs[attribute]); - }); - }; - /* const triggerEvent = (el, event_type) => { - if (el.fireEvent) { - el.fireEvent('on' + event_type); - } - else { - let evObj = document.createEvent('Events'); - evObj.initEvent(event_type, true, false); - el.dispatchEvent(evObj); - } - }*/ - var unSelectLinks = function unSelectLinks(elts) { - elts.forEach(function (link_node) { - var _setAttributes; - - setAttributes(link_node, (_setAttributes = {}, _defineProperty(_setAttributes, ATTR_SELECTED, 'false'), _defineProperty(_setAttributes, 'tabindex', '-1'), _setAttributes)); - }); - }; - var unSelectContents = function unSelectContents(elts) { - elts.forEach(function (content_node) { - content_node.setAttribute(ATTR_HIDDEN, true); - }); - }; - - var selectLink = function selectLink(el) { - var _setAttributes2; - - var destination = findById(el.getAttribute(ATTR_CONTROLS)); - setAttributes(el, (_setAttributes2 = {}, _defineProperty(_setAttributes2, ATTR_SELECTED, 'true'), _defineProperty(_setAttributes2, 'tabindex', '0'), _setAttributes2)); - destination.removeAttribute(ATTR_HIDDEN); - setTimeout(function () { - el.focus(); - }, 0); - setTimeout(function () { - history.pushState(null, null, location.pathname + location.search + '#' + el.getAttribute(ATTR_CONTROLS)); - }, DELAY_HASH_UPDATE); - }; - - var selectLinkInList = function selectLinkInList(itemsList, linkList, contentList, param) { - var indice_trouve = undefined; - - itemsList.forEach(function (itemNode, index) { - if (itemNode.querySelector('.' + TABS_JS_LISTLINK).getAttribute(ATTR_SELECTED) === 'true') { - indice_trouve = index; - } - }); - unSelectLinks(linkList); - unSelectContents(contentList); - if (param === 'next') { - selectLink(linkList[indice_trouve + 1]); - setTimeout(function () { - linkList[indice_trouve + 1].focus(); - }, 0); - } - if (param === 'prev') { - selectLink(linkList[indice_trouve - 1]); - setTimeout(function () { - linkList[indice_trouve - 1].focus(); - }, 0); - } - }; - - /* gets an element el, search if it is child of parent class, returns id of the parent */ - var searchParent = function searchParent(el, parentClass) { - var found = false; - var parentElement = el.parentNode; - while (parentElement && found === false) { - if (hasClass(parentElement, parentClass) === true) { - found = true; - } else { - parentElement = parentElement.parentNode; - } - } - if (found === true) { - return parentElement.getAttribute('id'); - } else { - return ''; - } - }; - - // Find all TABS - var $listTabs = function $listTabs() { - return [].slice.call(doc.querySelectorAll('.' + TABS_JS)); - }; - - var onLoad = function onLoad() { - - $listTabs().forEach(function (tabs_node, index) { - - var iLisible = index + 1; - var prefixClassName = tabs_node.hasAttribute(TABS_DATA_PREFIX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_PREFIX_CLASS) + '-' : ''; - var hx = tabs_node.hasAttribute(TABS_DATA_HX) === true ? tabs_node.getAttribute(TABS_DATA_HX) : ''; - var hxGeneratedClass = tabs_node.hasAttribute(TABS_DATA_GENERATED_HX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_GENERATED_HX_CLASS) : TABS_HX_DEFAULT_CLASS; - var existingHx = tabs_node.hasAttribute(TABS_DATA_EXISTING_HX) === true ? tabs_node.getAttribute(TABS_DATA_EXISTING_HX) : ''; - var $tabList = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LIST)); - var $tabListItems = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTITEM)); - var $tabListLinks = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTLINK)); - var $tabListPanels = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_CONTENT)); - var noTabSelected = true; - - // container - addClass(tabs_node, prefixClassName + TABS_STYLE); - tabs_node.setAttribute('id', TABS_STYLE + iLisible); - - // ul - $tabList.forEach(function (tabList) { - var _setAttributes3; - - addClass(tabList, prefixClassName + TABS_LIST_STYLE); - setAttributes(tabList, (_setAttributes3 = {}, _defineProperty(_setAttributes3, ATTR_ROLE, TABS_ROLE_TABLIST), _defineProperty(_setAttributes3, 'id', TABS_LIST_STYLE + iLisible), _setAttributes3)); - }); - // li - $tabListItems.forEach(function (tabListItem, index) { - var _setAttributes4; - - addClass(tabListItem, prefixClassName + TABS_LISTITEM_STYLE); - setAttributes(tabListItem, (_setAttributes4 = {}, _defineProperty(_setAttributes4, ATTR_ROLE, TABS_ROLE_PRESENTATION), _defineProperty(_setAttributes4, 'id', TABS_LISTITEM_STYLE + iLisible + '-' + (index + 1)), _setAttributes4)); - }); - // a - $tabListLinks.forEach(function (tabListLink) { - var _setAttributes5, _setAttributes6; - - var idHref = tabListLink.getAttribute("href").replace('#', ''); - var panelControlled = findById(idHref); - var linkText = tabListLink.innerText; - var panelSelected = tabListLink.hasAttribute(TABS_DATA_SELECTED_TAB) === true; - - addClass(tabListLink, prefixClassName + TABS_LINK_STYLE); - setAttributes(tabListLink, (_setAttributes5 = { - 'id': TABS_PREFIX_IDS + idHref - }, _defineProperty(_setAttributes5, ATTR_ROLE, TABS_ROLE_TAB), _defineProperty(_setAttributes5, ATTR_CONTROLS, idHref), _defineProperty(_setAttributes5, 'tabindex', '-1'), _defineProperty(_setAttributes5, ATTR_SELECTED, 'false'), _setAttributes5)); - - // panel controlled - setAttributes(panelControlled, (_setAttributes6 = {}, _defineProperty(_setAttributes6, ATTR_HIDDEN, 'true'), _defineProperty(_setAttributes6, ATTR_ROLE, TABS_ROLE_TABPANEL), _defineProperty(_setAttributes6, ATTR_LABELLEDBY, TABS_PREFIX_IDS + idHref), _setAttributes6)); - addClass(panelControlled, prefixClassName + TABS_CONTENT_STYLE); - - // if already selected - if (panelSelected && noTabSelected) { - noTabSelected = false; - setAttributes(tabListLink, _defineProperty({ - 'tabindex': '0' - }, ATTR_SELECTED, 'true')); - setAttributes(panelControlled, _defineProperty({}, ATTR_HIDDEN, 'false')); - } - - // hx - if (hx !== '') { - var hx_node = document.createElement(hx); - hx_node.setAttribute('class', hxGeneratedClass); - hx_node.setAttribute('tabindex', '0'); - hx_node.innerHTML = linkText; - panelControlled.insertBefore(hx_node, panelControlled.firstChild); - } - // existingHx - - if (existingHx !== '') { - var $hx_existing = [].slice.call(panelControlled.querySelectorAll(existingHx + ':first-child')); - $hx_existing.forEach(function (hx_item) { - hx_item.setAttribute('tabindex', '0'); - }); - } - - tabListLink.removeAttribute('href'); - }); - - if (hash !== '') { - var nodeHashed = findById(hash); - if (nodeHashed !== null) { - // just in case of an dumb error - // search if hash is current tabs_node - if (tabs_node.querySelector('#' + hash) !== null) { - // search if hash is ON tabs - if (hasClass(nodeHashed, TABS_JS_CONTENT) === true) { - // unselect others - unSelectLinks($tabListLinks); - unSelectContents($tabListPanels); - // select this one - nodeHashed.removeAttribute(ATTR_HIDDEN); - var linkHashed = findById(TABS_PREFIX_IDS + hash); - setAttributes(linkHashed, _defineProperty({ - 'tabindex': '0' - }, ATTR_SELECTED, 'true')); - noTabSelected = false; - } else { - // search if hash is IN tabs - var panelParentId = searchParent(nodeHashed, TABS_JS_CONTENT); - if (panelParentId !== '') { - // unselect others - unSelectLinks($tabListLinks); - unSelectContents($tabListPanels); - // select this one - var panelParent = findById(panelParentId); - panelParent.removeAttribute(ATTR_HIDDEN); - var linkParent = findById(TABS_PREFIX_IDS + panelParentId); - setAttributes(linkParent, _defineProperty({ - 'tabindex': '0' - }, ATTR_SELECTED, 'true')); - noTabSelected = false; - } - } - } - } - } - - // if no selected => select first - if (noTabSelected === true) { - setAttributes($tabListLinks[0], _defineProperty({ - 'tabindex': '0' - }, ATTR_SELECTED, 'true')); - var panelFirst = findById($tabListLinks[0].getAttribute(ATTR_CONTROLS)); - panelFirst.removeAttribute(ATTR_HIDDEN); - } - }); - - // click on - ['click', 'keydown'].forEach(function (eventName) { - //let isCtrl = false; - - doc.body.addEventListener(eventName, function (e) { - - // click on a tab link or on something IN a tab link - var parentLink = searchParent(e.target, TABS_JS_LISTLINK); - if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'click') { - var linkSelected = hasClass(e.target, TABS_JS_LISTLINK) === true ? e.target : findById(parentLink); - var parentTabId = searchParent(e.target, TABS_JS); - var parentTab = findById(parentTabId); - //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - - // aria selected false on all links - unSelectLinks($parentListLinks); - // add aria-hidden on all tabs contents - unSelectContents($parentListContents); - // add aria selected on selected link + show linked panel - selectLink(linkSelected); - - e.preventDefault(); - } - - // Key down on tabs - if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'keydown') { - //let linkSelected = hasClass( e.target, TABS_JS_LISTLINK) === true ? e.target : findById( parentLink ); - var parentTabId = searchParent(e.target, TABS_JS); - var parentTab = findById(parentTabId); - var $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - var firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); - var lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); - - // strike home on a tab => 1st tab - if (e.keyCode === 36) { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - e.preventDefault(); - } - // strike end on a tab => last tab - else if (e.keyCode === 35) { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - e.preventDefault(); - } - // strike up or left on the tab => previous tab - else if ((e.keyCode === 37 || e.keyCode === 38) && !e.ctrlKey) { - if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - e.preventDefault(); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); - e.preventDefault(); - } - } - // strike down or right in the tab => next tab - else if ((e.keyCode === 40 || e.keyCode === 39) && !e.ctrlKey) { - if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - e.preventDefault(); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); - e.preventDefault(); - } - } - } - - // Key down in tab panels - var parentTabPanelId = searchParent(e.target, TABS_JS_CONTENT); - if (parentTabPanelId !== '' && eventName === 'keydown') { - (function () { - var linkSelected = findById(findById(parentTabPanelId).getAttribute(ATTR_LABELLEDBY)); - var parentTabId = searchParent(e.target, TABS_JS); - var parentTab = findById(parentTabId); - var $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - var firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); - var lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); - - // strike up + ctrl => go to header - if (e.keyCode === 38 && e.ctrlKey) { - setTimeout(function () { - linkSelected.focus(); - }, 0); - e.preventDefault(); - } - // strike pageup + ctrl => go to prev header - if (e.keyCode === 33 && e.ctrlKey) { - // go to header - linkSelected.focus(); - e.preventDefault(); - // then previous - if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); - } - } - // strike pagedown + ctrl => go to next header - if (e.keyCode === 34 && e.ctrlKey) { - // go to header - linkSelected.focus(); - e.preventDefault(); - // then next - if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); - } - } - })(); - } - - // click on a tab link - var parentLinkToPanelId = searchParent(e.target, TABS_JS_LINK_TO_TAB); - if ((hasClass(e.target, TABS_JS_LINK_TO_TAB) === true || parentLinkToPanelId !== '') && eventName === 'click') { - var panelSelectedId = hasClass(e.target, TABS_JS_LINK_TO_TAB) === true ? e.target.getAttribute('href').replace('#', '') : findById(parentLinkToPanelId).replace('#', ''); - var panelSelected = findById(panelSelectedId); - var buttonPanelSelected = findById(panelSelected.getAttribute(ATTR_LABELLEDBY)); - var parentTabId = searchParent(e.target, TABS_JS); - var parentTab = findById(parentTabId); - //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(buttonPanelSelected); - - e.preventDefault(); - } - }, true); - }); - document.removeEventListener('DOMContentLoaded', onLoad); - }; - - document.addEventListener('DOMContentLoaded', onLoad); +/* + * ES2015 accessible tabs panel system, using ARIA + * Website: https://van11y.net/accessible-tab-panel/ + * License MIT: https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE + */ +'use strict'; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +(function (doc) { + + 'use strict'; + + var TABS_JS = 'js-tabs'; + var TABS_JS_LIST = 'js-tablist'; + var TABS_JS_LISTITEM = 'js-tablist__item'; + var TABS_JS_LISTLINK = 'js-tablist__link'; + var TABS_JS_CONTENT = 'js-tabcontent'; + var TABS_JS_LINK_TO_TAB = 'js-link-to-tab'; + + var TABS_DATA_PREFIX_CLASS = 'data-tabs-prefix-class'; + var TABS_DATA_HX = 'data-hx'; + var TABS_DATA_GENERATED_HX_CLASS = 'data-tabs-generated-hx-class'; + var TABS_DATA_EXISTING_HX = 'data-existing-hx'; + + var TABS_DATA_SELECTED_TAB = 'data-selected'; + + var TABS_PREFIX_IDS = 'label_'; + + var TABS_STYLE = 'tabs'; + var TABS_LIST_STYLE = 'tabs__list'; + var TABS_LISTITEM_STYLE = 'tabs__item'; + var TABS_LINK_STYLE = 'tabs__link'; + var TABS_CONTENT_STYLE = 'tabs__content'; + + var TABS_HX_DEFAULT_CLASS = 'invisible'; + + var TABS_ROLE_TABLIST = 'tablist'; + var TABS_ROLE_TAB = 'tab'; + var TABS_ROLE_TABPANEL = 'tabpanel'; + var TABS_ROLE_PRESENTATION = 'presentation'; + + var ATTR_ROLE = 'role'; + var ATTR_LABELLEDBY = 'aria-labelledby'; + var ATTR_HIDDEN = 'aria-hidden'; + var ATTR_CONTROLS = 'aria-controls'; + var ATTR_SELECTED = 'aria-selected'; + + var DELAY_HASH_UPDATE = 1000; + + var hash = window.location.hash.replace('#', ''); + + //const IS_OPENED_CLASS = 'is-opened'; + + var findById = function findById(id) { + return doc.getElementById(id); + }; + + var addClass = function addClass(el, className) { + if (el.classList) { + el.classList.add(className); // IE 10+ + } else { + el.className += ' ' + className; // IE 8+ + } + }; + + /*const removeClass = (el, className) => { + if (el.classList) { + el.classList.remove(className); // IE 10+ + } + else { + el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ + } + }*/ + + var hasClass = function hasClass(el, className) { + if (el.classList) { + return el.classList.contains(className); // IE 10+ + } else { + return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? + } + }; + + var setAttributes = function setAttributes(node, attrs) { + Object.keys(attrs).forEach(function (attribute) { + node.setAttribute(attribute, attrs[attribute]); + }); + }; + var unSelectLinks = function unSelectLinks(elts) { + elts.forEach(function (link_node) { + var _setAttributes; + + setAttributes(link_node, (_setAttributes = {}, _defineProperty(_setAttributes, ATTR_SELECTED, 'false'), _defineProperty(_setAttributes, 'tabindex', '-1'), _setAttributes)); + }); + }; + var unSelectContents = function unSelectContents(elts) { + elts.forEach(function (content_node) { + content_node.setAttribute(ATTR_HIDDEN, true); + }); + }; + + var selectLink = function selectLink(el) { + var _setAttributes2; + + var destination = findById(el.getAttribute(ATTR_CONTROLS)); + setAttributes(el, (_setAttributes2 = {}, _defineProperty(_setAttributes2, ATTR_SELECTED, 'true'), _defineProperty(_setAttributes2, 'tabindex', '0'), _setAttributes2)); + destination.removeAttribute(ATTR_HIDDEN); + setTimeout(function () { + el.focus(); + }, 0); + setTimeout(function () { + history.pushState(null, null, location.pathname + location.search + '#' + el.getAttribute(ATTR_CONTROLS)); + }, DELAY_HASH_UPDATE); + }; + + var selectLinkInList = function selectLinkInList(itemsList, linkList, contentList, param) { + var indice_trouve = undefined; + + itemsList.forEach(function (itemNode, index) { + if (itemNode.querySelector('.' + TABS_JS_LISTLINK).getAttribute(ATTR_SELECTED) === 'true') { + indice_trouve = index; + } + }); + unSelectLinks(linkList); + unSelectContents(contentList); + if (param === 'next') { + selectLink(linkList[indice_trouve + 1]); + setTimeout(function () { + linkList[indice_trouve + 1].focus(); + }, 0); + } + if (param === 'prev') { + selectLink(linkList[indice_trouve - 1]); + setTimeout(function () { + linkList[indice_trouve - 1].focus(); + }, 0); + } + }; + + /* gets an element el, search if it is child of parent class, returns id of the parent */ + var searchParent = function searchParent(el, parentClass) { + var found = false; + var parentElement = el.parentNode; + while (parentElement && found === false) { + if (hasClass(parentElement, parentClass) === true) { + found = true; + } else { + parentElement = parentElement.parentNode; + } + } + if (found === true) { + return parentElement.getAttribute('id'); + } else { + return ''; + } + }; + + /** Find all tabs inside a container + * @param {Node} node Default document + * @return {Array} + */ + var $listTabs = function $listTabs() { + var node = arguments.length <= 0 || arguments[0] === undefined ? doc : arguments[0]; + return [].slice.call(node.querySelectorAll('.' + TABS_JS)); + }; + + /** + * Build tooltips for a container + * @param {Node} node + */ + var attach = function attach(node) { + + $listTabs(node).forEach(function (tabs_node) { + + var iLisible = Math.random().toString(32).slice(2, 12); + var prefixClassName = tabs_node.hasAttribute(TABS_DATA_PREFIX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_PREFIX_CLASS) + '-' : ''; + var hx = tabs_node.hasAttribute(TABS_DATA_HX) === true ? tabs_node.getAttribute(TABS_DATA_HX) : ''; + var hxGeneratedClass = tabs_node.hasAttribute(TABS_DATA_GENERATED_HX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_GENERATED_HX_CLASS) : TABS_HX_DEFAULT_CLASS; + var existingHx = tabs_node.hasAttribute(TABS_DATA_EXISTING_HX) === true ? tabs_node.getAttribute(TABS_DATA_EXISTING_HX) : ''; + var $tabList = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LIST)); + var $tabListItems = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTITEM)); + var $tabListLinks = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTLINK)); + var $tabListPanels = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_CONTENT)); + var noTabSelected = true; + + // container + addClass(tabs_node, prefixClassName + TABS_STYLE); + tabs_node.setAttribute('id', TABS_STYLE + iLisible); + + // ul + $tabList.forEach(function (tabList) { + var _setAttributes3; + + addClass(tabList, prefixClassName + TABS_LIST_STYLE); + setAttributes(tabList, (_setAttributes3 = {}, _defineProperty(_setAttributes3, ATTR_ROLE, TABS_ROLE_TABLIST), _defineProperty(_setAttributes3, 'id', TABS_LIST_STYLE + iLisible), _setAttributes3)); + }); + // li + $tabListItems.forEach(function (tabListItem, index) { + var _setAttributes4; + + addClass(tabListItem, prefixClassName + TABS_LISTITEM_STYLE); + setAttributes(tabListItem, (_setAttributes4 = {}, _defineProperty(_setAttributes4, ATTR_ROLE, TABS_ROLE_PRESENTATION), _defineProperty(_setAttributes4, 'id', TABS_LISTITEM_STYLE + iLisible + '-' + (index + 1)), _setAttributes4)); + }); + // a + $tabListLinks.forEach(function (tabListLink) { + var _setAttributes5, _setAttributes6; + + var idHref = tabListLink.getAttribute("href").replace('#', ''); + var panelControlled = findById(idHref); + var linkText = tabListLink.innerText; + var panelSelected = tabListLink.hasAttribute(TABS_DATA_SELECTED_TAB) === true; + + addClass(tabListLink, prefixClassName + TABS_LINK_STYLE); + setAttributes(tabListLink, (_setAttributes5 = { + 'id': TABS_PREFIX_IDS + idHref + }, _defineProperty(_setAttributes5, ATTR_ROLE, TABS_ROLE_TAB), _defineProperty(_setAttributes5, ATTR_CONTROLS, idHref), _defineProperty(_setAttributes5, 'tabindex', '-1'), _defineProperty(_setAttributes5, ATTR_SELECTED, 'false'), _setAttributes5)); + + // panel controlled + setAttributes(panelControlled, (_setAttributes6 = {}, _defineProperty(_setAttributes6, ATTR_HIDDEN, 'true'), _defineProperty(_setAttributes6, ATTR_ROLE, TABS_ROLE_TABPANEL), _defineProperty(_setAttributes6, ATTR_LABELLEDBY, TABS_PREFIX_IDS + idHref), _setAttributes6)); + addClass(panelControlled, prefixClassName + TABS_CONTENT_STYLE); + + // if already selected + if (panelSelected && noTabSelected) { + noTabSelected = false; + setAttributes(tabListLink, _defineProperty({ + 'tabindex': '0' + }, ATTR_SELECTED, 'true')); + setAttributes(panelControlled, _defineProperty({}, ATTR_HIDDEN, 'false')); + } + + // hx + if (hx !== '') { + var hx_node = document.createElement(hx); + hx_node.setAttribute('class', hxGeneratedClass); + hx_node.setAttribute('tabindex', '0'); + hx_node.innerHTML = linkText; + panelControlled.insertBefore(hx_node, panelControlled.firstChild); + } + // existingHx + + if (existingHx !== '') { + var $hx_existing = [].slice.call(panelControlled.querySelectorAll(existingHx + ':first-child')); + $hx_existing.forEach(function (hx_item) { + hx_item.setAttribute('tabindex', '0'); + }); + } + + tabListLink.removeAttribute('href'); + }); + + if (hash !== '') { + var nodeHashed = findById(hash); + if (nodeHashed !== null) { + // just in case of an dumb error + // search if hash is current tabs_node + if (tabs_node.querySelector('#' + hash) !== null) { + // search if hash is ON tabs + if (hasClass(nodeHashed, TABS_JS_CONTENT) === true) { + // unselect others + unSelectLinks($tabListLinks); + unSelectContents($tabListPanels); + // select this one + nodeHashed.removeAttribute(ATTR_HIDDEN); + var linkHashed = findById(TABS_PREFIX_IDS + hash); + setAttributes(linkHashed, _defineProperty({ + 'tabindex': '0' + }, ATTR_SELECTED, 'true')); + noTabSelected = false; + } else { + // search if hash is IN tabs + var panelParentId = searchParent(nodeHashed, TABS_JS_CONTENT); + if (panelParentId !== '') { + // unselect others + unSelectLinks($tabListLinks); + unSelectContents($tabListPanels); + // select this one + var panelParent = findById(panelParentId); + panelParent.removeAttribute(ATTR_HIDDEN); + var linkParent = findById(TABS_PREFIX_IDS + panelParentId); + setAttributes(linkParent, _defineProperty({ + 'tabindex': '0' + }, ATTR_SELECTED, 'true')); + noTabSelected = false; + } + } + } + } + } + + // if no selected => select first + if (noTabSelected === true) { + setAttributes($tabListLinks[0], _defineProperty({ + 'tabindex': '0' + }, ATTR_SELECTED, 'true')); + var panelFirst = findById($tabListLinks[0].getAttribute(ATTR_CONTROLS)); + panelFirst.removeAttribute(ATTR_HIDDEN); + } + }); + }; + + /* listeners */ + ['click', 'keydown'].forEach(function (eventName) { + //let isCtrl = false; + + doc.body.addEventListener(eventName, function (e) { + + // click on a tab link or on something IN a tab link + var parentLink = searchParent(e.target, TABS_JS_LISTLINK); + if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'click') { + var linkSelected = hasClass(e.target, TABS_JS_LISTLINK) === true ? e.target : findById(parentLink); + var parentTabId = searchParent(e.target, TABS_JS); + var parentTab = findById(parentTabId); + //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + + // aria selected false on all links + unSelectLinks($parentListLinks); + // add aria-hidden on all tabs contents + unSelectContents($parentListContents); + // add aria selected on selected link + show linked panel + selectLink(linkSelected); + + e.preventDefault(); + } + + // Key down on tabs + if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'keydown') { + //let linkSelected = hasClass( e.target, TABS_JS_LISTLINK) === true ? e.target : findById( parentLink ); + var parentTabId = searchParent(e.target, TABS_JS); + var parentTab = findById(parentTabId); + var $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + var firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); + var lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); + + // strike home on a tab => 1st tab + if (e.keyCode === 36) { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + e.preventDefault(); + } + // strike end on a tab => last tab + else if (e.keyCode === 35) { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + e.preventDefault(); + } + // strike up or left on the tab => previous tab + else if ((e.keyCode === 37 || e.keyCode === 38) && !e.ctrlKey) { + if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + e.preventDefault(); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); + e.preventDefault(); + } + } + // strike down or right in the tab => next tab + else if ((e.keyCode === 40 || e.keyCode === 39) && !e.ctrlKey) { + if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + e.preventDefault(); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); + e.preventDefault(); + } + } + } + + // Key down in tab panels + var parentTabPanelId = searchParent(e.target, TABS_JS_CONTENT); + if (parentTabPanelId !== '' && eventName === 'keydown') { + (function () { + var linkSelected = findById(findById(parentTabPanelId).getAttribute(ATTR_LABELLEDBY)); + var parentTabId = searchParent(e.target, TABS_JS); + var parentTab = findById(parentTabId); + var $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + var firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); + var lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); + + // strike up + ctrl => go to header + if (e.keyCode === 38 && e.ctrlKey) { + setTimeout(function () { + linkSelected.focus(); + }, 0); + e.preventDefault(); + } + // strike pageup + ctrl => go to prev header + if (e.keyCode === 33 && e.ctrlKey) { + // go to header + linkSelected.focus(); + e.preventDefault(); + // then previous + if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); + } + } + // strike pagedown + ctrl => go to next header + if (e.keyCode === 34 && e.ctrlKey) { + // go to header + linkSelected.focus(); + e.preventDefault(); + // then next + if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); + } + } + })(); + } + + // click on a tab link + var parentLinkToPanelId = searchParent(e.target, TABS_JS_LINK_TO_TAB); + if ((hasClass(e.target, TABS_JS_LINK_TO_TAB) === true || parentLinkToPanelId !== '') && eventName === 'click') { + var panelSelectedId = hasClass(e.target, TABS_JS_LINK_TO_TAB) === true ? e.target.getAttribute('href').replace('#', '') : findById(parentLinkToPanelId).replace('#', ''); + var panelSelected = findById(panelSelectedId); + var buttonPanelSelected = findById(panelSelected.getAttribute(ATTR_LABELLEDBY)); + var parentTabId = searchParent(e.target, TABS_JS); + var parentTab = findById(parentTabId); + //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + var $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + var $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(buttonPanelSelected); + + e.preventDefault(); + } + }, true); + }); + + var onLoad = function onLoad() { + attach(); + document.removeEventListener('DOMContentLoaded', onLoad); + }; + + document.addEventListener('DOMContentLoaded', onLoad); + + window.van11yAccessibleTabPanelAria = attach; })(document); \ No newline at end of file diff --git a/dist/van11y-accessible-tab-panel-aria.min.js b/dist/van11y-accessible-tab-panel-aria.min.js index 749c7db..1ea187f 100644 --- a/dist/van11y-accessible-tab-panel-aria.min.js +++ b/dist/van11y-accessible-tab-panel-aria.min.js @@ -1,7 +1,7 @@ /** * van11y-accessible-tab-panel-aria - ES2015 accessible tabs panel system, using ARIA (compatible IE9+ when transpiled) - * @version v1.1.0 + * @version v2.0.0 * @link https://van11y.net/accessible-tab-panel/ * @license MIT : https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE */ -"use strict";function _defineProperty(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}!function(e){var t="js-tabs",r="js-tablist",i="js-tablist__item",l="js-tablist__link",n="js-tabcontent",a="js-link-to-tab",c="data-tabs-prefix-class",o="data-hx",u="data-tabs-generated-hx-class",s="data-existing-hx",f="data-selected",d="label_",b="tabs",y="tabs__list",v="tabs__item",A="tabs__link",p="tabs__content",g="invisible",_="tablist",h="tab",m="tabpanel",S="presentation",q="role",P="aria-labelledby",k="aria-hidden",x="aria-controls",E="aria-selected",D=1e3,C=window.location.hash.replace("#",""),L=function(t){return e.getElementById(t)},j=function(e,t){e.classList?e.classList.add(t):e.className+=" "+t},w=function(e,t){return e.classList?e.classList.contains(t):new RegExp("(^| )"+t+"( |$)","gi").test(e.className)},T=function(e,t){Object.keys(t).forEach(function(r){e.setAttribute(r,t[r])})},K=function(e){e.forEach(function(e){var t;T(e,(t={},_defineProperty(t,E,"false"),_defineProperty(t,"tabindex","-1"),t))})},N=function(e){e.forEach(function(e){e.setAttribute(k,!0)})},O=function(e){var t,r=L(e.getAttribute(x));T(e,(t={},_defineProperty(t,E,"true"),_defineProperty(t,"tabindex","0"),t)),r.removeAttribute(k),setTimeout(function(){e.focus()},0),setTimeout(function(){history.pushState(null,null,location.pathname+location.search+"#"+e.getAttribute(x))},D)},M=function(e,t,r,i){var n=void 0;e.forEach(function(e,t){"true"===e.querySelector("."+l).getAttribute(E)&&(n=t)}),K(t),N(r),"next"===i&&(O(t[n+1]),setTimeout(function(){t[n+1].focus()},0)),"prev"===i&&(O(t[n-1]),setTimeout(function(){t[n-1].focus()},0))},B=function(e,t){for(var r=!1,i=e.parentNode;i&&r===!1;)w(i,t)===!0?r=!0:i=i.parentNode;return r===!0?i.getAttribute("id"):""},H=function(){return[].slice.call(e.querySelectorAll("."+t))},I=function R(){H().forEach(function(e,t){var a=t+1,D=e.hasAttribute(c)===!0?e.getAttribute(c)+"-":"",O=e.hasAttribute(o)===!0?e.getAttribute(o):"",M=e.hasAttribute(u)===!0?e.getAttribute(u):g,H=e.hasAttribute(s)===!0?e.getAttribute(s):"",I=[].slice.call(e.querySelectorAll("."+r)),R=[].slice.call(e.querySelectorAll("."+i)),$=[].slice.call(e.querySelectorAll("."+l)),z=[].slice.call(e.querySelectorAll("."+n)),F=!0;if(j(e,D+b),e.setAttribute("id",b+a),I.forEach(function(e){var t;j(e,D+y),T(e,(t={},_defineProperty(t,q,_),_defineProperty(t,"id",y+a),t))}),R.forEach(function(e,t){var r;j(e,D+v),T(e,(r={},_defineProperty(r,q,S),_defineProperty(r,"id",v+a+"-"+(t+1)),r))}),$.forEach(function(e){var t,r,i=e.getAttribute("href").replace("#",""),l=L(i),n=e.innerText,a=e.hasAttribute(f)===!0;if(j(e,D+A),T(e,(t={id:d+i},_defineProperty(t,q,h),_defineProperty(t,x,i),_defineProperty(t,"tabindex","-1"),_defineProperty(t,E,"false"),t)),T(l,(r={},_defineProperty(r,k,"true"),_defineProperty(r,q,m),_defineProperty(r,P,d+i),r)),j(l,D+p),a&&F&&(F=!1,T(e,_defineProperty({tabindex:"0"},E,"true")),T(l,_defineProperty({},k,"false"))),""!==O){var c=document.createElement(O);c.setAttribute("class",M),c.setAttribute("tabindex","0"),c.innerHTML=n,l.insertBefore(c,l.firstChild)}if(""!==H){var o=[].slice.call(l.querySelectorAll(H+":first-child"));o.forEach(function(e){e.setAttribute("tabindex","0")})}e.removeAttribute("href")}),""!==C){var G=L(C);if(null!==G&&null!==e.querySelector("#"+C))if(w(G,n)===!0){K($),N(z),G.removeAttribute(k);var J=L(d+C);T(J,_defineProperty({tabindex:"0"},E,"true")),F=!1}else{var Q=B(G,n);if(""!==Q){K($),N(z);var U=L(Q);U.removeAttribute(k);var V=L(d+Q);T(V,_defineProperty({tabindex:"0"},E,"true")),F=!1}}}if(F===!0){T($[0],_defineProperty({tabindex:"0"},E,"true"));var W=L($[0].getAttribute(x));W.removeAttribute(k)}}),["click","keydown"].forEach(function(r){e.body.addEventListener(r,function(e){var c=B(e.target,l);if((w(e.target,l)===!0||""!==c)&&"click"===r){var o=w(e.target,l)===!0?e.target:L(c),u=B(e.target,t),s=L(u),f=[].slice.call(s.querySelectorAll("."+l)),d=[].slice.call(s.querySelectorAll("."+n));K(f),N(d),O(o),e.preventDefault()}if((w(e.target,l)===!0||""!==c)&&"keydown"===r){var u=B(e.target,t),s=L(u),b=[].slice.call(s.querySelectorAll("."+i)),f=[].slice.call(s.querySelectorAll("."+l)),d=[].slice.call(s.querySelectorAll("."+n)),y=b[0].querySelector("."+l),v=b[b.length-1].querySelector("."+l);36===e.keyCode?(K(f),N(d),O(y),e.preventDefault()):35===e.keyCode?(K(f),N(d),O(v),e.preventDefault()):37!==e.keyCode&&38!==e.keyCode||e.ctrlKey?40!==e.keyCode&&39!==e.keyCode||e.ctrlKey||("true"===v.getAttribute(E)?(K(f),N(d),O(y),e.preventDefault()):(M(b,f,d,"next"),e.preventDefault())):"true"===y.getAttribute(E)?(K(f),N(d),O(v),e.preventDefault()):(M(b,f,d,"prev"),e.preventDefault())}var A=B(e.target,n);""!==A&&"keydown"===r&&!function(){var r=L(L(A).getAttribute(P)),a=B(e.target,t),c=L(a),o=[].slice.call(c.querySelectorAll("."+i)),u=[].slice.call(c.querySelectorAll("."+l)),s=[].slice.call(c.querySelectorAll("."+n)),f=o[0].querySelector("."+l),d=o[o.length-1].querySelector("."+l);38===e.keyCode&&e.ctrlKey&&(setTimeout(function(){r.focus()},0),e.preventDefault()),33===e.keyCode&&e.ctrlKey&&(r.focus(),e.preventDefault(),"true"===f.getAttribute(E)?(K(u),N(s),O(d)):M(o,u,s,"prev")),34===e.keyCode&&e.ctrlKey&&(r.focus(),e.preventDefault(),"true"===d.getAttribute(E)?(K(u),N(s),O(f)):M(o,u,s,"next"))}();var p=B(e.target,a);if((w(e.target,a)===!0||""!==p)&&"click"===r){var g=w(e.target,a)===!0?e.target.getAttribute("href").replace("#",""):L(p).replace("#",""),_=L(g),h=L(_.getAttribute(P)),u=B(e.target,t),s=L(u),f=[].slice.call(s.querySelectorAll("."+l)),d=[].slice.call(s.querySelectorAll("."+n));K(f),N(d),O(h),e.preventDefault()}},!0)}),document.removeEventListener("DOMContentLoaded",R)};document.addEventListener("DOMContentLoaded",I)}(document); \ No newline at end of file +"use strict";function _defineProperty(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}!function(e){var t="js-tabs",r="js-tablist",i="js-tablist__item",l="js-tablist__link",n="js-tabcontent",a="js-link-to-tab",o="data-tabs-prefix-class",c="data-hx",u="data-tabs-generated-hx-class",s="data-existing-hx",f="data-selected",d="label_",b="tabs",y="tabs__list",v="tabs__item",A="tabs__link",p="tabs__content",g="invisible",h="tablist",_="tab",m="tabpanel",S="presentation",P="role",q="aria-labelledby",k="aria-hidden",x="aria-controls",E="aria-selected",D=1e3,C=window.location.hash.replace("#",""),L=function(t){return e.getElementById(t)},w=function(e,t){e.classList?e.classList.add(t):e.className+=" "+t},j=function(e,t){return e.classList?e.classList.contains(t):new RegExp("(^| )"+t+"( |$)","gi").test(e.className)},T=function(e,t){Object.keys(t).forEach(function(r){e.setAttribute(r,t[r])})},K=function(e){e.forEach(function(e){var t;T(e,(t={},_defineProperty(t,E,"false"),_defineProperty(t,"tabindex","-1"),t))})},M=function(e){e.forEach(function(e){e.setAttribute(k,!0)})},N=function(e){var t,r=L(e.getAttribute(x));T(e,(t={},_defineProperty(t,E,"true"),_defineProperty(t,"tabindex","0"),t)),r.removeAttribute(k),setTimeout(function(){e.focus()},0),setTimeout(function(){history.pushState(null,null,location.pathname+location.search+"#"+e.getAttribute(x))},D)},O=function(e,t,r,i){var n=void 0;e.forEach(function(e,t){"true"===e.querySelector("."+l).getAttribute(E)&&(n=t)}),K(t),M(r),"next"===i&&(N(t[n+1]),setTimeout(function(){t[n+1].focus()},0)),"prev"===i&&(N(t[n-1]),setTimeout(function(){t[n-1].focus()},0))},B=function(e,t){for(var r=!1,i=e.parentNode;i&&r===!1;)j(i,t)===!0?r=!0:i=i.parentNode;return r===!0?i.getAttribute("id"):""},H=function(){var r=arguments.length<=0||void 0===arguments[0]?e:arguments[0];return[].slice.call(r.querySelectorAll("."+t))},I=function(e){H(e).forEach(function(e){var t=Math.random().toString(32).slice(2,12),a=e.hasAttribute(o)===!0?e.getAttribute(o)+"-":"",D=e.hasAttribute(c)===!0?e.getAttribute(c):"",N=e.hasAttribute(u)===!0?e.getAttribute(u):g,O=e.hasAttribute(s)===!0?e.getAttribute(s):"",H=[].slice.call(e.querySelectorAll("."+r)),I=[].slice.call(e.querySelectorAll("."+i)),R=[].slice.call(e.querySelectorAll("."+l)),$=[].slice.call(e.querySelectorAll("."+n)),z=!0;if(w(e,a+b),e.setAttribute("id",b+t),H.forEach(function(e){var r;w(e,a+y),T(e,(r={},_defineProperty(r,P,h),_defineProperty(r,"id",y+t),r))}),I.forEach(function(e,r){var i;w(e,a+v),T(e,(i={},_defineProperty(i,P,S),_defineProperty(i,"id",v+t+"-"+(r+1)),i))}),R.forEach(function(e){var t,r,i=e.getAttribute("href").replace("#",""),l=L(i),n=e.innerText,o=e.hasAttribute(f)===!0;if(w(e,a+A),T(e,(t={id:d+i},_defineProperty(t,P,_),_defineProperty(t,x,i),_defineProperty(t,"tabindex","-1"),_defineProperty(t,E,"false"),t)),T(l,(r={},_defineProperty(r,k,"true"),_defineProperty(r,P,m),_defineProperty(r,q,d+i),r)),w(l,a+p),o&&z&&(z=!1,T(e,_defineProperty({tabindex:"0"},E,"true")),T(l,_defineProperty({},k,"false"))),""!==D){var c=document.createElement(D);c.setAttribute("class",N),c.setAttribute("tabindex","0"),c.innerHTML=n,l.insertBefore(c,l.firstChild)}if(""!==O){var u=[].slice.call(l.querySelectorAll(O+":first-child"));u.forEach(function(e){e.setAttribute("tabindex","0")})}e.removeAttribute("href")}),""!==C){var F=L(C);if(null!==F&&null!==e.querySelector("#"+C))if(j(F,n)===!0){K(R),M($),F.removeAttribute(k);var G=L(d+C);T(G,_defineProperty({tabindex:"0"},E,"true")),z=!1}else{var J=B(F,n);if(""!==J){K(R),M($);var Q=L(J);Q.removeAttribute(k);var U=L(d+J);T(U,_defineProperty({tabindex:"0"},E,"true")),z=!1}}}if(z===!0){T(R[0],_defineProperty({tabindex:"0"},E,"true"));var V=L(R[0].getAttribute(x));V.removeAttribute(k)}})};["click","keydown"].forEach(function(r){e.body.addEventListener(r,function(e){var o=B(e.target,l);if((j(e.target,l)===!0||""!==o)&&"click"===r){var c=j(e.target,l)===!0?e.target:L(o),u=B(e.target,t),s=L(u),f=[].slice.call(s.querySelectorAll("."+l)),d=[].slice.call(s.querySelectorAll("."+n));K(f),M(d),N(c),e.preventDefault()}if((j(e.target,l)===!0||""!==o)&&"keydown"===r){var u=B(e.target,t),s=L(u),b=[].slice.call(s.querySelectorAll("."+i)),f=[].slice.call(s.querySelectorAll("."+l)),d=[].slice.call(s.querySelectorAll("."+n)),y=b[0].querySelector("."+l),v=b[b.length-1].querySelector("."+l);36===e.keyCode?(K(f),M(d),N(y),e.preventDefault()):35===e.keyCode?(K(f),M(d),N(v),e.preventDefault()):37!==e.keyCode&&38!==e.keyCode||e.ctrlKey?40!==e.keyCode&&39!==e.keyCode||e.ctrlKey||("true"===v.getAttribute(E)?(K(f),M(d),N(y),e.preventDefault()):(O(b,f,d,"next"),e.preventDefault())):"true"===y.getAttribute(E)?(K(f),M(d),N(v),e.preventDefault()):(O(b,f,d,"prev"),e.preventDefault())}var A=B(e.target,n);""!==A&&"keydown"===r&&!function(){var r=L(L(A).getAttribute(q)),a=B(e.target,t),o=L(a),c=[].slice.call(o.querySelectorAll("."+i)),u=[].slice.call(o.querySelectorAll("."+l)),s=[].slice.call(o.querySelectorAll("."+n)),f=c[0].querySelector("."+l),d=c[c.length-1].querySelector("."+l);38===e.keyCode&&e.ctrlKey&&(setTimeout(function(){r.focus()},0),e.preventDefault()),33===e.keyCode&&e.ctrlKey&&(r.focus(),e.preventDefault(),"true"===f.getAttribute(E)?(K(u),M(s),N(d)):O(c,u,s,"prev")),34===e.keyCode&&e.ctrlKey&&(r.focus(),e.preventDefault(),"true"===d.getAttribute(E)?(K(u),M(s),N(f)):O(c,u,s,"next"))}();var p=B(e.target,a);if((j(e.target,a)===!0||""!==p)&&"click"===r){var g=j(e.target,a)===!0?e.target.getAttribute("href").replace("#",""):L(p).replace("#",""),h=L(g),_=L(h.getAttribute(q)),u=B(e.target,t),s=L(u),f=[].slice.call(s.querySelectorAll("."+l)),d=[].slice.call(s.querySelectorAll("."+n));K(f),M(d),N(_),e.preventDefault()}},!0)});var R=function $(){I(),document.removeEventListener("DOMContentLoaded",$)};document.addEventListener("DOMContentLoaded",R),window.van11yAccessibleTabPanelAria=I}(document); \ No newline at end of file diff --git a/package.json b/package.json index ed75af2..10e234c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "name": "van11y-accessible-tab-panel-aria", "description": "ES2015 accessible tabs panel system, using ARIA (compatible IE9+ when transpiled)", "main": "dist/van11y-accessible-tab-panel-aria.min.js", - "version": "1.1.0", + "version": "2.0.0", "keywords": [ "Accessibility", "ARIA", diff --git a/src/van11y-accessible-tab-panel-aria.es6.js b/src/van11y-accessible-tab-panel-aria.es6.js index c6f3ece..70cfd4d 100644 --- a/src/van11y-accessible-tab-panel-aria.es6.js +++ b/src/van11y-accessible-tab-panel-aria.es6.js @@ -1,488 +1,491 @@ -/* - * ES2015 accessible tabs panel system, using ARIA - * Website: https://van11y.net/accessible-tab-panel/ - * License MIT: https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE - */ -(doc => { - - 'use strict'; - - const TABS_JS = 'js-tabs'; - const TABS_JS_LIST = 'js-tablist'; - const TABS_JS_LISTITEM = 'js-tablist__item'; - const TABS_JS_LISTLINK = 'js-tablist__link'; - const TABS_JS_CONTENT = 'js-tabcontent'; - const TABS_JS_LINK_TO_TAB = 'js-link-to-tab'; - - const TABS_DATA_PREFIX_CLASS = 'data-tabs-prefix-class'; - const TABS_DATA_HX = 'data-hx'; - const TABS_DATA_GENERATED_HX_CLASS = 'data-tabs-generated-hx-class'; - const TABS_DATA_EXISTING_HX = 'data-existing-hx'; - - const TABS_DATA_SELECTED_TAB = 'data-selected'; - - const TABS_PREFIX_IDS = 'label_'; - - const TABS_STYLE = 'tabs'; - const TABS_LIST_STYLE = 'tabs__list'; - const TABS_LISTITEM_STYLE = 'tabs__item'; - const TABS_LINK_STYLE = 'tabs__link'; - const TABS_CONTENT_STYLE = 'tabs__content'; - - const TABS_HX_DEFAULT_CLASS = 'invisible'; - - const TABS_ROLE_TABLIST = 'tablist'; - const TABS_ROLE_TAB = 'tab'; - const TABS_ROLE_TABPANEL = 'tabpanel'; - const TABS_ROLE_PRESENTATION = 'presentation'; - - const ATTR_ROLE = 'role'; - const ATTR_LABELLEDBY = 'aria-labelledby'; - const ATTR_HIDDEN = 'aria-hidden'; - const ATTR_CONTROLS = 'aria-controls'; - const ATTR_SELECTED = 'aria-selected'; - - const DELAY_HASH_UPDATE = 1000; - - let hash = window.location.hash.replace('#', ''); - - //const IS_OPENED_CLASS = 'is-opened'; - - - - const findById = id => doc.getElementById(id); - - const addClass = (el, className) => { - if (el.classList) { - el.classList.add(className); // IE 10+ - } else { - el.className += ' ' + className; // IE 8+ - } - } - - /*const removeClass = (el, className) => { - if (el.classList) { - el.classList.remove(className); // IE 10+ - } - else { - el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ - } - }*/ - - const hasClass = (el, className) => { - if (el.classList) { - return el.classList.contains(className); // IE 10+ - } else { - return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? - } - } - - const setAttributes = (node, attrs) => { - Object - .keys(attrs) - .forEach((attribute) => { - node.setAttribute(attribute, attrs[attribute]); - }); - }; - /* const triggerEvent = (el, event_type) => { - if (el.fireEvent) { - el.fireEvent('on' + event_type); - } - else { - let evObj = document.createEvent('Events'); - evObj.initEvent(event_type, true, false); - el.dispatchEvent(evObj); - } - }*/ - const unSelectLinks = (elts) => { - elts - .forEach((link_node) => { - setAttributes(link_node, { - [ATTR_SELECTED]: 'false', - 'tabindex': '-1' - }); - }); - } - const unSelectContents = (elts) => { - elts - .forEach((content_node) => { - content_node.setAttribute(ATTR_HIDDEN, true); - }); - } - - const selectLink = (el) => { - let destination = findById(el.getAttribute(ATTR_CONTROLS)); - setAttributes(el, { - [ATTR_SELECTED]: 'true', - 'tabindex': '0' - }); - destination.removeAttribute(ATTR_HIDDEN); - setTimeout(function() { - el.focus(); - }, 0); - setTimeout(function() { - history.pushState(null, null, location.pathname + location.search + '#' + el.getAttribute(ATTR_CONTROLS)) - }, DELAY_HASH_UPDATE); - } - - const selectLinkInList = (itemsList, linkList, contentList, param) => { - let indice_trouve; - - itemsList - .forEach((itemNode, index) => { - if (itemNode.querySelector('.' + TABS_JS_LISTLINK).getAttribute(ATTR_SELECTED) === 'true') { - indice_trouve = index; - } - }); - unSelectLinks(linkList); - unSelectContents(contentList); - if (param === 'next') { - selectLink(linkList[indice_trouve + 1]); - setTimeout(function() { - linkList[indice_trouve + 1].focus(); - }, 0); - } - if (param === 'prev') { - selectLink(linkList[indice_trouve - 1]); - setTimeout(function() { - linkList[indice_trouve - 1].focus(); - }, 0); - } - - } - - /* gets an element el, search if it is child of parent class, returns id of the parent */ - let searchParent = (el, parentClass) => { - let found = false; - let parentElement = el.parentNode; - while (parentElement && found === false) { - if (hasClass(parentElement, parentClass) === true) { - found = true; - } else { - parentElement = parentElement.parentNode; - } - } - if (found === true) { - return parentElement.getAttribute('id'); - } else { - return ''; - } - } - - - // Find all TABS - const $listTabs = () => [].slice.call(doc.querySelectorAll('.' + TABS_JS)); - - - const onLoad = () => { - - $listTabs() - .forEach((tabs_node, index) => { - - let iLisible = index + 1; - let prefixClassName = tabs_node.hasAttribute(TABS_DATA_PREFIX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_PREFIX_CLASS) + '-' : ''; - let hx = tabs_node.hasAttribute(TABS_DATA_HX) === true ? tabs_node.getAttribute(TABS_DATA_HX) : ''; - let hxGeneratedClass = tabs_node.hasAttribute(TABS_DATA_GENERATED_HX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_GENERATED_HX_CLASS) : TABS_HX_DEFAULT_CLASS; - let existingHx = tabs_node.hasAttribute(TABS_DATA_EXISTING_HX) === true ? tabs_node.getAttribute(TABS_DATA_EXISTING_HX) : ''; - let $tabList = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LIST)); - let $tabListItems = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $tabListLinks = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $tabListPanels = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_CONTENT)); - let noTabSelected = true; - - // container - addClass(tabs_node, prefixClassName + TABS_STYLE); - tabs_node.setAttribute('id', TABS_STYLE + iLisible); - - // ul - $tabList.forEach((tabList) => { - addClass(tabList, prefixClassName + TABS_LIST_STYLE); - setAttributes(tabList, { - [ATTR_ROLE]: TABS_ROLE_TABLIST, - 'id': TABS_LIST_STYLE + iLisible - }); - }); - // li - $tabListItems.forEach((tabListItem, index) => { - addClass(tabListItem, prefixClassName + TABS_LISTITEM_STYLE); - setAttributes(tabListItem, { - [ATTR_ROLE]: TABS_ROLE_PRESENTATION, - 'id': TABS_LISTITEM_STYLE + iLisible + '-' + (index + 1) - }); - }); - // a - $tabListLinks.forEach((tabListLink) => { - let idHref = tabListLink.getAttribute("href").replace('#', ''); - let panelControlled = findById(idHref); - let linkText = tabListLink.innerText; - let panelSelected = tabListLink.hasAttribute(TABS_DATA_SELECTED_TAB) === true; - - addClass(tabListLink, prefixClassName + TABS_LINK_STYLE); - setAttributes(tabListLink, { - 'id' : TABS_PREFIX_IDS + idHref, - [ATTR_ROLE]: TABS_ROLE_TAB, - [ATTR_CONTROLS]: idHref, - 'tabindex': '-1', - [ATTR_SELECTED]: 'false' - }); - - // panel controlled - setAttributes(panelControlled, { - [ATTR_HIDDEN]: 'true', - [ATTR_ROLE]: TABS_ROLE_TABPANEL, - [ATTR_LABELLEDBY]: TABS_PREFIX_IDS + idHref - }); - addClass(panelControlled, prefixClassName + TABS_CONTENT_STYLE); - - // if already selected - if (panelSelected && noTabSelected) { - noTabSelected = false; - setAttributes(tabListLink, { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - setAttributes(panelControlled, { - [ATTR_HIDDEN]: 'false' - }); - } - - // hx - if (hx !== '') { - let hx_node = document.createElement(hx); - hx_node.setAttribute('class', hxGeneratedClass); - hx_node.setAttribute('tabindex', '0'); - hx_node.innerHTML = linkText; - panelControlled.insertBefore(hx_node, panelControlled.firstChild); - } - // existingHx - - if (existingHx !== '') { - let $hx_existing = [].slice.call(panelControlled.querySelectorAll(existingHx + ':first-child')); - $hx_existing.forEach((hx_item) => { - hx_item.setAttribute('tabindex', '0'); - }); - - } - - tabListLink.removeAttribute('href'); - - }); - - - if (hash !== '') { - let nodeHashed = findById(hash); - if (nodeHashed !== null) { // just in case of an dumb error - // search if hash is current tabs_node - if (tabs_node.querySelector('#' + hash) !== null) { - // search if hash is ON tabs - if (hasClass(nodeHashed, TABS_JS_CONTENT) === true) { - // unselect others - unSelectLinks($tabListLinks); - unSelectContents($tabListPanels); - // select this one - nodeHashed.removeAttribute(ATTR_HIDDEN); - let linkHashed = findById(TABS_PREFIX_IDS + hash); - setAttributes(linkHashed, { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - noTabSelected = false; - } else { - // search if hash is IN tabs - let panelParentId = searchParent(nodeHashed, TABS_JS_CONTENT); - if (panelParentId !== '') { - // unselect others - unSelectLinks($tabListLinks); - unSelectContents($tabListPanels); - // select this one - let panelParent = findById(panelParentId); - panelParent.removeAttribute(ATTR_HIDDEN); - let linkParent = findById(TABS_PREFIX_IDS + panelParentId); - setAttributes(linkParent, { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - noTabSelected = false; - } - } - } - } - } - - // if no selected => select first - if (noTabSelected === true) { - setAttributes($tabListLinks[0], { - 'tabindex': '0', - [ATTR_SELECTED]: 'true' - }); - let panelFirst = findById($tabListLinks[0].getAttribute(ATTR_CONTROLS)); - panelFirst.removeAttribute(ATTR_HIDDEN); - } - - }); - - // click on - ['click', 'keydown'] - .forEach(eventName => { - //let isCtrl = false; - - doc.body - .addEventListener(eventName, e => { - - // click on a tab link or on something IN a tab link - let parentLink = searchParent(e.target, TABS_JS_LISTLINK); - if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'click') { - let linkSelected = hasClass(e.target, TABS_JS_LISTLINK) === true ? e.target : findById(parentLink); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - - // aria selected false on all links - unSelectLinks($parentListLinks); - // add aria-hidden on all tabs contents - unSelectContents($parentListContents); - // add aria selected on selected link + show linked panel - selectLink(linkSelected); - - e.preventDefault(); - } - - // Key down on tabs - if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'keydown') { - //let linkSelected = hasClass( e.target, TABS_JS_LISTLINK) === true ? e.target : findById( parentLink ); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); - let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); - - // strike home on a tab => 1st tab - if (e.keyCode === 36) { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - e.preventDefault(); - } - // strike end on a tab => last tab - else if (e.keyCode === 35) { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - e.preventDefault(); - } - // strike up or left on the tab => previous tab - else if ((e.keyCode === 37 || e.keyCode === 38) && !e.ctrlKey) { - if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - e.preventDefault(); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); - e.preventDefault(); - } - } - // strike down or right in the tab => next tab - else if ((e.keyCode === 40 || e.keyCode === 39) && !e.ctrlKey) { - if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - e.preventDefault(); - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); - e.preventDefault(); - } - } - - } - - // Key down in tab panels - let parentTabPanelId = searchParent(e.target, TABS_JS_CONTENT); - if (parentTabPanelId !== '' && eventName === 'keydown') { - let linkSelected = findById(findById(parentTabPanelId).getAttribute(ATTR_LABELLEDBY)); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); - let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); - - - // strike up + ctrl => go to header - if (e.keyCode === 38 && e.ctrlKey) { - setTimeout(function() { - linkSelected.focus(); - }, 0); - e.preventDefault(); - } - // strike pageup + ctrl => go to prev header - if (e.keyCode === 33 && e.ctrlKey) { - // go to header - linkSelected.focus(); - e.preventDefault(); - // then previous - if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(lastLink); - - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); - } - - } - // strike pagedown + ctrl => go to next header - if (e.keyCode === 34 && e.ctrlKey) { - // go to header - linkSelected.focus(); - e.preventDefault(); - // then next - if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(firstLink); - - } else { - selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); - } - } - } - - // click on a tab link - let parentLinkToPanelId = searchParent(e.target, TABS_JS_LINK_TO_TAB); - if ((hasClass(e.target, TABS_JS_LINK_TO_TAB) === true || parentLinkToPanelId !== '') && eventName === 'click') { - let panelSelectedId = hasClass(e.target, TABS_JS_LINK_TO_TAB) === true ? e.target.getAttribute('href').replace('#', '') : findById(parentLinkToPanelId).replace('#', ''); - let panelSelected = findById(panelSelectedId); - let buttonPanelSelected = findById(panelSelected.getAttribute(ATTR_LABELLEDBY)); - let parentTabId = searchParent(e.target, TABS_JS); - let parentTab = findById(parentTabId); - //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); - let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); - let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); - - unSelectLinks($parentListLinks); - unSelectContents($parentListContents); - selectLink(buttonPanelSelected); - - e.preventDefault(); - } - - }, true); - }); - document.removeEventListener('DOMContentLoaded', onLoad); - } - - document.addEventListener('DOMContentLoaded', onLoad); - - +/* + * ES2015 accessible tabs panel system, using ARIA + * Website: https://van11y.net/accessible-tab-panel/ + * License MIT: https://github.com/nico3333fr/van11y-accessible-tab-panel-aria/blob/master/LICENSE + */ +(doc => { + + 'use strict'; + + const TABS_JS = 'js-tabs'; + const TABS_JS_LIST = 'js-tablist'; + const TABS_JS_LISTITEM = 'js-tablist__item'; + const TABS_JS_LISTLINK = 'js-tablist__link'; + const TABS_JS_CONTENT = 'js-tabcontent'; + const TABS_JS_LINK_TO_TAB = 'js-link-to-tab'; + + const TABS_DATA_PREFIX_CLASS = 'data-tabs-prefix-class'; + const TABS_DATA_HX = 'data-hx'; + const TABS_DATA_GENERATED_HX_CLASS = 'data-tabs-generated-hx-class'; + const TABS_DATA_EXISTING_HX = 'data-existing-hx'; + + const TABS_DATA_SELECTED_TAB = 'data-selected'; + + const TABS_PREFIX_IDS = 'label_'; + + const TABS_STYLE = 'tabs'; + const TABS_LIST_STYLE = 'tabs__list'; + const TABS_LISTITEM_STYLE = 'tabs__item'; + const TABS_LINK_STYLE = 'tabs__link'; + const TABS_CONTENT_STYLE = 'tabs__content'; + + const TABS_HX_DEFAULT_CLASS = 'invisible'; + + const TABS_ROLE_TABLIST = 'tablist'; + const TABS_ROLE_TAB = 'tab'; + const TABS_ROLE_TABPANEL = 'tabpanel'; + const TABS_ROLE_PRESENTATION = 'presentation'; + + const ATTR_ROLE = 'role'; + const ATTR_LABELLEDBY = 'aria-labelledby'; + const ATTR_HIDDEN = 'aria-hidden'; + const ATTR_CONTROLS = 'aria-controls'; + const ATTR_SELECTED = 'aria-selected'; + + const DELAY_HASH_UPDATE = 1000; + + let hash = window.location.hash.replace('#', ''); + + //const IS_OPENED_CLASS = 'is-opened'; + + + + const findById = id => doc.getElementById(id); + + const addClass = (el, className) => { + if (el.classList) { + el.classList.add(className); // IE 10+ + } else { + el.className += ' ' + className; // IE 8+ + } + } + + /*const removeClass = (el, className) => { + if (el.classList) { + el.classList.remove(className); // IE 10+ + } + else { + el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+ + } + }*/ + + const hasClass = (el, className) => { + if (el.classList) { + return el.classList.contains(className); // IE 10+ + } else { + return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ? + } + } + + const setAttributes = (node, attrs) => { + Object + .keys(attrs) + .forEach((attribute) => { + node.setAttribute(attribute, attrs[attribute]); + }); + }; + const unSelectLinks = (elts) => { + elts + .forEach((link_node) => { + setAttributes(link_node, { + [ATTR_SELECTED]: 'false', + 'tabindex': '-1' + }); + }); + } + const unSelectContents = (elts) => { + elts + .forEach((content_node) => { + content_node.setAttribute(ATTR_HIDDEN, true); + }); + } + + const selectLink = (el) => { + let destination = findById(el.getAttribute(ATTR_CONTROLS)); + setAttributes(el, { + [ATTR_SELECTED]: 'true', + 'tabindex': '0' + }); + destination.removeAttribute(ATTR_HIDDEN); + setTimeout(function() { + el.focus(); + }, 0); + setTimeout(function() { + history.pushState(null, null, location.pathname + location.search + '#' + el.getAttribute(ATTR_CONTROLS)) + }, DELAY_HASH_UPDATE); + } + + const selectLinkInList = (itemsList, linkList, contentList, param) => { + let indice_trouve; + + itemsList + .forEach((itemNode, index) => { + if (itemNode.querySelector('.' + TABS_JS_LISTLINK).getAttribute(ATTR_SELECTED) === 'true') { + indice_trouve = index; + } + }); + unSelectLinks(linkList); + unSelectContents(contentList); + if (param === 'next') { + selectLink(linkList[indice_trouve + 1]); + setTimeout(function() { + linkList[indice_trouve + 1].focus(); + }, 0); + } + if (param === 'prev') { + selectLink(linkList[indice_trouve - 1]); + setTimeout(function() { + linkList[indice_trouve - 1].focus(); + }, 0); + } + + } + + /* gets an element el, search if it is child of parent class, returns id of the parent */ + let searchParent = (el, parentClass) => { + let found = false; + let parentElement = el.parentNode; + while (parentElement && found === false) { + if (hasClass(parentElement, parentClass) === true) { + found = true; + } else { + parentElement = parentElement.parentNode; + } + } + if (found === true) { + return parentElement.getAttribute('id'); + } else { + return ''; + } + } + + + /** Find all tabs inside a container + * @param {Node} node Default document + * @return {Array} + */ + const $listTabs = (node = doc) => [].slice.call(node.querySelectorAll('.' + TABS_JS)); + + + /** + * Build tooltips for a container + * @param {Node} node + */ + const attach = (node) => { + + $listTabs(node) + .forEach((tabs_node) => { + + let iLisible = Math.random().toString(32).slice(2, 12); + let prefixClassName = tabs_node.hasAttribute(TABS_DATA_PREFIX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_PREFIX_CLASS) + '-' : ''; + let hx = tabs_node.hasAttribute(TABS_DATA_HX) === true ? tabs_node.getAttribute(TABS_DATA_HX) : ''; + let hxGeneratedClass = tabs_node.hasAttribute(TABS_DATA_GENERATED_HX_CLASS) === true ? tabs_node.getAttribute(TABS_DATA_GENERATED_HX_CLASS) : TABS_HX_DEFAULT_CLASS; + let existingHx = tabs_node.hasAttribute(TABS_DATA_EXISTING_HX) === true ? tabs_node.getAttribute(TABS_DATA_EXISTING_HX) : ''; + let $tabList = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LIST)); + let $tabListItems = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $tabListLinks = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $tabListPanels = [].slice.call(tabs_node.querySelectorAll('.' + TABS_JS_CONTENT)); + let noTabSelected = true; + + // container + addClass(tabs_node, prefixClassName + TABS_STYLE); + tabs_node.setAttribute('id', TABS_STYLE + iLisible); + + // ul + $tabList.forEach((tabList) => { + addClass(tabList, prefixClassName + TABS_LIST_STYLE); + setAttributes(tabList, { + [ATTR_ROLE]: TABS_ROLE_TABLIST, + 'id': TABS_LIST_STYLE + iLisible + }); + }); + // li + $tabListItems.forEach((tabListItem, index) => { + addClass(tabListItem, prefixClassName + TABS_LISTITEM_STYLE); + setAttributes(tabListItem, { + [ATTR_ROLE]: TABS_ROLE_PRESENTATION, + 'id': TABS_LISTITEM_STYLE + iLisible + '-' + (index + 1) + }); + }); + // a + $tabListLinks.forEach((tabListLink) => { + let idHref = tabListLink.getAttribute("href").replace('#', ''); + let panelControlled = findById(idHref); + let linkText = tabListLink.innerText; + let panelSelected = tabListLink.hasAttribute(TABS_DATA_SELECTED_TAB) === true; + + addClass(tabListLink, prefixClassName + TABS_LINK_STYLE); + setAttributes(tabListLink, { + 'id': TABS_PREFIX_IDS + idHref, + [ATTR_ROLE]: TABS_ROLE_TAB, + [ATTR_CONTROLS]: idHref, + 'tabindex': '-1', + [ATTR_SELECTED]: 'false' + }); + + // panel controlled + setAttributes(panelControlled, { + [ATTR_HIDDEN]: 'true', + [ATTR_ROLE]: TABS_ROLE_TABPANEL, + [ATTR_LABELLEDBY]: TABS_PREFIX_IDS + idHref + }); + addClass(panelControlled, prefixClassName + TABS_CONTENT_STYLE); + + // if already selected + if (panelSelected && noTabSelected) { + noTabSelected = false; + setAttributes(tabListLink, { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + setAttributes(panelControlled, { + [ATTR_HIDDEN]: 'false' + }); + } + + // hx + if (hx !== '') { + let hx_node = document.createElement(hx); + hx_node.setAttribute('class', hxGeneratedClass); + hx_node.setAttribute('tabindex', '0'); + hx_node.innerHTML = linkText; + panelControlled.insertBefore(hx_node, panelControlled.firstChild); + } + // existingHx + + if (existingHx !== '') { + let $hx_existing = [].slice.call(panelControlled.querySelectorAll(existingHx + ':first-child')); + $hx_existing.forEach((hx_item) => { + hx_item.setAttribute('tabindex', '0'); + }); + + } + + tabListLink.removeAttribute('href'); + + }); + + + if (hash !== '') { + let nodeHashed = findById(hash); + if (nodeHashed !== null) { // just in case of an dumb error + // search if hash is current tabs_node + if (tabs_node.querySelector('#' + hash) !== null) { + // search if hash is ON tabs + if (hasClass(nodeHashed, TABS_JS_CONTENT) === true) { + // unselect others + unSelectLinks($tabListLinks); + unSelectContents($tabListPanels); + // select this one + nodeHashed.removeAttribute(ATTR_HIDDEN); + let linkHashed = findById(TABS_PREFIX_IDS + hash); + setAttributes(linkHashed, { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + noTabSelected = false; + } else { + // search if hash is IN tabs + let panelParentId = searchParent(nodeHashed, TABS_JS_CONTENT); + if (panelParentId !== '') { + // unselect others + unSelectLinks($tabListLinks); + unSelectContents($tabListPanels); + // select this one + let panelParent = findById(panelParentId); + panelParent.removeAttribute(ATTR_HIDDEN); + let linkParent = findById(TABS_PREFIX_IDS + panelParentId); + setAttributes(linkParent, { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + noTabSelected = false; + } + } + } + } + } + + // if no selected => select first + if (noTabSelected === true) { + setAttributes($tabListLinks[0], { + 'tabindex': '0', + [ATTR_SELECTED]: 'true' + }); + let panelFirst = findById($tabListLinks[0].getAttribute(ATTR_CONTROLS)); + panelFirst.removeAttribute(ATTR_HIDDEN); + } + + }); + }; + + /* listeners */ + ['click', 'keydown'] + .forEach(eventName => { + //let isCtrl = false; + + doc.body + .addEventListener(eventName, e => { + + // click on a tab link or on something IN a tab link + let parentLink = searchParent(e.target, TABS_JS_LISTLINK); + if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'click') { + let linkSelected = hasClass(e.target, TABS_JS_LISTLINK) === true ? e.target : findById(parentLink); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + + // aria selected false on all links + unSelectLinks($parentListLinks); + // add aria-hidden on all tabs contents + unSelectContents($parentListContents); + // add aria selected on selected link + show linked panel + selectLink(linkSelected); + + e.preventDefault(); + } + + // Key down on tabs + if ((hasClass(e.target, TABS_JS_LISTLINK) === true || parentLink !== '') && eventName === 'keydown') { + //let linkSelected = hasClass( e.target, TABS_JS_LISTLINK) === true ? e.target : findById( parentLink ); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); + let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); + + // strike home on a tab => 1st tab + if (e.keyCode === 36) { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + e.preventDefault(); + } + // strike end on a tab => last tab + else if (e.keyCode === 35) { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + e.preventDefault(); + } + // strike up or left on the tab => previous tab + else if ((e.keyCode === 37 || e.keyCode === 38) && !e.ctrlKey) { + if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + e.preventDefault(); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); + e.preventDefault(); + } + } + // strike down or right in the tab => next tab + else if ((e.keyCode === 40 || e.keyCode === 39) && !e.ctrlKey) { + if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + e.preventDefault(); + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); + e.preventDefault(); + } + } + + } + + // Key down in tab panels + let parentTabPanelId = searchParent(e.target, TABS_JS_CONTENT); + if (parentTabPanelId !== '' && eventName === 'keydown') { + let linkSelected = findById(findById(parentTabPanelId).getAttribute(ATTR_LABELLEDBY)); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + let firstLink = $parentListItems[0].querySelector('.' + TABS_JS_LISTLINK); + let lastLink = $parentListItems[$parentListItems.length - 1].querySelector('.' + TABS_JS_LISTLINK); + + + // strike up + ctrl => go to header + if (e.keyCode === 38 && e.ctrlKey) { + setTimeout(function() { + linkSelected.focus(); + }, 0); + e.preventDefault(); + } + // strike pageup + ctrl => go to prev header + if (e.keyCode === 33 && e.ctrlKey) { + // go to header + linkSelected.focus(); + e.preventDefault(); + // then previous + if (firstLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(lastLink); + + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'prev'); + } + + } + // strike pagedown + ctrl => go to next header + if (e.keyCode === 34 && e.ctrlKey) { + // go to header + linkSelected.focus(); + e.preventDefault(); + // then next + if (lastLink.getAttribute(ATTR_SELECTED) === 'true') { + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(firstLink); + + } else { + selectLinkInList($parentListItems, $parentListLinks, $parentListContents, 'next'); + } + } + } + + // click on a tab link + let parentLinkToPanelId = searchParent(e.target, TABS_JS_LINK_TO_TAB); + if ((hasClass(e.target, TABS_JS_LINK_TO_TAB) === true || parentLinkToPanelId !== '') && eventName === 'click') { + let panelSelectedId = hasClass(e.target, TABS_JS_LINK_TO_TAB) === true ? e.target.getAttribute('href').replace('#', '') : findById(parentLinkToPanelId).replace('#', ''); + let panelSelected = findById(panelSelectedId); + let buttonPanelSelected = findById(panelSelected.getAttribute(ATTR_LABELLEDBY)); + let parentTabId = searchParent(e.target, TABS_JS); + let parentTab = findById(parentTabId); + //let $parentListItems = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTITEM)); + let $parentListLinks = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_LISTLINK)); + let $parentListContents = [].slice.call(parentTab.querySelectorAll('.' + TABS_JS_CONTENT)); + + unSelectLinks($parentListLinks); + unSelectContents($parentListContents); + selectLink(buttonPanelSelected); + + e.preventDefault(); + } + + }, true); + }); + + const onLoad = () => { + attach(); + document.removeEventListener('DOMContentLoaded', onLoad); + } + + document.addEventListener('DOMContentLoaded', onLoad); + + window.van11yAccessibleTabPanelAria = attach; + + })(document); \ No newline at end of file