Current File : /home/pacjaorg/.trash/media.1/vendor/joomla-custom-elements/js/joomla-tab.js |
class TabElement extends HTMLElement {}
customElements.define('joomla-tab-element', TabElement);
class TabsElement extends HTMLElement {
/* Attributes to monitor */
static get observedAttributes() { return ['recall', 'orientation', 'view', 'breakpoint']; }
get recall() { return this.getAttribute('recall'); }
set recall(value) { this.setAttribute('recall', value); }
get view() { return this.getAttribute('view'); }
set view(value) { this.setAttribute('view', value); }
get orientation() { return this.getAttribute('orientation'); }
set orientation(value) { this.setAttribute('orientation', value); }
get breakpoint() { return parseInt(this.getAttribute('breakpoint'), 10); }
set breakpoint(value) { this.setAttribute('breakpoint', value); }
/* Lifecycle, element created */
constructor() {
super();
this.tabs = [];
this.tabsElements = [];
this.previousActive = null;
this.onMutation = this.onMutation.bind(this);
this.keyBehaviour = this.keyBehaviour.bind(this);
this.activateTab = this.activateTab.bind(this);
this.deactivateTabs = this.deactivateTabs.bind(this);
this.checkView = this.checkView.bind(this);
this.observer = new MutationObserver(this.onMutation);
this.observer.observe(this, { attributes: false, childList: true, subtree: true });
}
/* Lifecycle, element appended to the DOM */
connectedCallback() {
if (!this.orientation || (this.orientation && !['horizontal', 'vertical'].includes(this.orientation))) {
this.orientation = 'horizontal';
}
if (!this.view || (this.view && !['tabs', 'accordion'].includes(this.view))) {
this.view = 'tabs';
}
// get tab elements
this.tabsElements = [].slice.call(this.children).filter((el) => el.tagName.toLowerCase() === 'joomla-tab-element');
// Sanity checks
if (!this.tabsElements.length) {
return;
}
this.isNested = this.parentNode.closest('joomla-tab') instanceof HTMLElement;
this.hydrate();
if (this.hasAttribute('recall') && !this.isNested) {
this.activateFromState();
}
// Activate tab from the URL hash
if (window.location.hash) {
const hash = window.location.hash.substr(1);
const tabToactivate = this.tabs.filter((tab) => tab.tab.id === hash);
if (tabToactivate.length) {
this.activateTab(tabToactivate[0].tab, false);
}
}
// If no active tab activate the first one
if (!this.tabs.filter((tab) => tab.tab.hasAttribute('active')).length) {
this.activateTab(this.tabs[0].tab, false);
}
this.addEventListener('keyup', this.keyBehaviour);
if (this.breakpoint) {
// Convert tabs to accordian
this.checkView();
window.addEventListener('resize', () => {
this.checkView();
});
}
}
/* Lifecycle, element removed from the DOM */
disconnectedCallback() {
this.tabs.map((tab) => {
tab.tabButton.removeEventListener('click', this.activateTab);
tab.accordionButton.removeEventListener('click', this.activateTab);
return tab;
});
this.removeEventListener('keyup', this.keyBehaviour);
}
/* Respond to attribute changes */
attributeChangedCallback(attr, oldValue, newValue) {
switch (attr) {
case 'view':
if (!newValue || (newValue && !['tabs', 'accordion'].includes(newValue))) {
this.view = 'tabs';
}
if (newValue === 'tabs' && newValue !== oldValue) {
if (this.tabButtonContainer) this.tabButtonContainer.removeAttribute('hidden');
this.tabs.map((tab) => tab.accordionButton.setAttribute('hidden', ''));
} else if (newValue === 'accordion' && newValue !== oldValue) {
if (this.tabButtonContainer) this.tabButtonContainer.setAttribute('hidden', '');
this.tabs.map((tab) => tab.accordionButton.removeAttribute('hidden'));
}
break;
}
}
hydrate() {
// Ensure the tab links container exists
this.tabButtonContainer = document.createElement('div');
this.tabButtonContainer.setAttribute('role', 'tablist');
this.insertAdjacentElement('afterbegin', this.tabButtonContainer);
if (this.view === 'accordion') {
this.tabButtonContainer.setAttribute('hidden', '');
}
this.tabsElements.map((tab) => {
// Create Accordion button
const accordionButton = document.createElement('button');
accordionButton.setAttribute('aria-expanded', !!tab.hasAttribute('active'));
accordionButton.setAttribute('aria-controls', tab.id);
accordionButton.setAttribute('type', 'button');
accordionButton.innerHTML = `<span class="accordion-title">${tab.getAttribute('name')}<span class="accordion-icon"></span></span>`;
tab.insertAdjacentElement('beforebegin', accordionButton);
if (this.view === 'tabs') {
accordionButton.setAttribute('hidden', '');
}
accordionButton.addEventListener('click', this.activateTab);
// Create tab button
const tabButton = document.createElement('button');
tabButton.setAttribute('aria-expanded', !!tab.hasAttribute('active'));
tabButton.setAttribute('aria-controls', tab.id);
tabButton.setAttribute('role', 'tab');
tabButton.setAttribute('type', 'button');
tabButton.innerHTML = `${tab.getAttribute('name')}`;
this.tabButtonContainer.appendChild(tabButton);
tabButton.addEventListener('click', this.activateTab);
if (this.view === 'tabs') {
tab.setAttribute('role', 'tabpanel');
} else {
tab.setAttribute('role', 'region');
}
this.tabs.push({
tab,
tabButton,
accordionButton,
});
return tab;
});
}
/* Update on mutation */
onMutation(mutationsList) {
// eslint-disable-next-line no-restricted-syntax
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length) {
[].slice.call(mutation.addedNodes).map((inserted) => this.createNavs(inserted));
// Add the tab buttons
}
if (mutation.removedNodes.length) {
// Remove the tab buttons
[].slice.call(mutation.addedNodes).map((inserted) => this.removeNavs(inserted));
}
}
}
}
keyBehaviour(e) {
// Only the tabs/accordion buttons, no ⌘ or Alt modifier
if (![...this.tabs.map((el) => el.tabButton), ...this.tabs.map((el) => el.accordionButton)]
.includes(document.activeElement)
|| e.metaKey
|| e.altKey) {
return;
}
let previousTabItem;
let nextTabItem;
if (this.view === 'tabs') {
const currentTabIndex = this.tabs.findIndex((tab) => tab.tab.hasAttribute('active'));
previousTabItem = currentTabIndex - 1 >= 0
? this.tabs[currentTabIndex - 1] : this.tabs[this.tabs.length - 1];
nextTabItem = currentTabIndex + 1 <= this.tabs.length - 1
? this.tabs[currentTabIndex + 1] : this.tabs[0];
} else {
const currentTabIndex = this.tabs.map((el) => el.accordionButton)
.findIndex((tab) => tab === document.activeElement);
previousTabItem = currentTabIndex - 1 >= 0
? this.tabs[currentTabIndex - 1] : this.tabs[this.tabs.length - 1];
nextTabItem = currentTabIndex + 1 <= this.tabs.length - 1
? this.tabs[currentTabIndex + 1] : this.tabs[0];
}
// catch left/right and up/down arrow key events
switch (e.keyCode) {
case 37:
case 38:
if (this.view === 'tabs') {
previousTabItem.tabButton.click();
previousTabItem.tabButton.focus();
} else {
previousTabItem.accordionButton.focus();
}
e.preventDefault();
break;
case 39:
case 40:
if (this.view === 'tabs') {
nextTabItem.tabButton.click();
nextTabItem.tabButton.focus();
} else {
nextTabItem.accordionButton.focus();
}
e.preventDefault();
break;
}
}
deactivateTabs() {
this.tabs.map((tabObj) => {
tabObj.accordionButton.removeAttribute('aria-disabled');
tabObj.tabButton.removeAttribute('aria-expanded');
tabObj.accordionButton.setAttribute('aria-expanded', false);
if (tabObj.tab.hasAttribute('active')) {
this.dispatchCustomEvent('joomla.tab.hide', this.view === 'tabs' ? tabObj.tabButton : tabObj.accordionButton, this.previousActive);
tabObj.tab.removeAttribute('active');
tabObj.tab.setAttribute('tabindex', '-1');
// Emit hidden event
this.dispatchCustomEvent('joomla.tab.hidden', this.view === 'tabs' ? tabObj.tabButton : tabObj.accordionButton, this.previousActive);
this.previousActive = this.view === 'tabs' ? tabObj.tabButton : tabObj.accordionButton;
}
return tabObj;
});
}
activateTab(input, state = true) {
let currentTrigger;
if (input.currentTarget) {
currentTrigger = this.tabs.find((tab) => ((this.view === 'tabs' ? tab.tabButton : tab.accordionButton) === input.currentTarget));
} else if (input instanceof HTMLElement) {
currentTrigger = this.tabs.find((tab) => tab.tab === input);
} else if (Number.isInteger(input)) {
currentTrigger = this.tabs[input];
}
if (currentTrigger) {
// Accordion can close the active panel
if (this.view === 'accordion' && this.tabs.find((tab) => tab.accordionButton.getAttribute('aria-expanded') === 'true') === currentTrigger) {
if (currentTrigger.tab.hasAttribute('active')) {
currentTrigger.tab.removeAttribute('active');
return;
}
currentTrigger.tab.setAttribute('active', '');
return;
}
// Remove current active
this.deactivateTabs();
// Set new active
currentTrigger.tabButton.setAttribute('aria-expanded', true);
currentTrigger.accordionButton.setAttribute('aria-expanded', true);
currentTrigger.accordionButton.setAttribute('aria-disabled', true);
currentTrigger.tab.setAttribute('active', '');
currentTrigger.tabButton.removeAttribute('tabindex');
this.dispatchCustomEvent('joomla.tab.show', this.view === 'tabs' ? currentTrigger.tabButton : currentTrigger.accordionButton, this.previousActive);
if (state) {
if (this.view === 'tabs') {
currentTrigger.tabButton.focus();
} else {
currentTrigger.accordionButton.focus();
}
}
if (state) this.saveState(currentTrigger.tab.id);
this.dispatchCustomEvent('joomla.tab.shown', this.view === 'tabs' ? currentTrigger.tabButton : currentTrigger.accordionButton, this.previousActive);
}
}
// Create navigation elements for inserted tabs
createNavs(tab) {
if ((tab instanceof Element && tab.tagName.toLowerCase() !== 'joomla-tab-element') || ![].some.call(this.children, (el) => el === tab).length || !tab.getAttribute('name') || !tab.getAttribute('id')) return;
const tabs = [].slice.call(this.children).filter((el) => el.tagName.toLowerCase() === 'joomla-tab-element');
const index = tabs.findIndex((tb) => tb === tab);
// Create Accordion button
const accordionButton = document.createElement('button');
accordionButton.setAttribute('aria-expanded', !!tab.hasAttribute('active'));
accordionButton.setAttribute('aria-controls', tab.id);
accordionButton.setAttribute('type', 'button');
accordionButton.innerHTML = `<span class="accordion-title">${tab.getAttribute('name')}<span class="accordion-icon"></span></span>`;
tab.insertAdjacentElement('beforebegin', accordionButton);
if (this.view === 'tabs') {
accordionButton.setAttribute('hidden', '');
}
accordionButton.addEventListener('click', this.activateTab);
// Create tab button
const tabButton = document.createElement('button');
tabButton.setAttribute('aria-expanded', !!tab.hasAttribute('active'));
tabButton.setAttribute('aria-controls', tab.id);
tabButton.setAttribute('role', 'tab');
tabButton.setAttribute('type', 'button');
tabButton.innerHTML = `${tab.getAttribute('name')}`;
if (tabs.length - 1 === index) {
// last
this.tabButtonContainer.appendChild(tabButton);
this.tabs.push({
tab,
tabButton,
accordionButton,
});
} else if (index === 0) {
// first
this.tabButtonContainer.insertAdjacentElement('afterbegin', tabButton);
this.tabs.slice(0, 0, {
tab,
tabButton,
accordionButton,
});
} else {
// Middle
this.tabs[index - 1].tabButton.insertAdjacentElement('afterend', tabButton);
this.tabs.slice(index - 1, 0, {
tab,
tabButton,
accordionButton,
});
}
tabButton.addEventListener('click', this.activateTab);
}
// Remove navigation elements for removed tabs
removeNavs(tab) {
if ((tab instanceof Element && tab.tagName.toLowerCase() !== 'joomla-tab-element') || ![].some.call(this.children, (el) => el === tab).length || !tab.getAttribute('name') || !tab.getAttribute('id')) return;
const accordionButton = tab.previousSilbingElement;
if (accordionButton && accordionButton.tagName.toLowerCase() === 'button') {
accordionButton.removeEventListener('click', this.keyBehaviour);
accordionButton.parentNode.removeChild(accordionButton);
}
const tabButton = this.tabButtonContainer.querySelector(`[aria-controls=${accordionButton.id}]`);
if (tabButton) {
tabButton.removeEventListener('click', this.keyBehaviour);
tabButton.parentNode.removeChild(tabButton);
}
const index = this.tabs.findIndex((tb) => tb.tabs === tab);
if (index - 1 === 0) {
this.tabs.shift();
} else if (index - 1 === this.tabs.length) {
this.tabs.pop();
} else {
this.tabs.splice(index - 1, 1);
}
}
/** Method to convert tabs to accordion and vice versa depending on screen size */
checkView() {
if (!this.breakpoint) {
return;
}
if (document.body.getBoundingClientRect().width > this.breakpoint) {
if (this.view === 'tabs') {
return;
}
this.tabButtonContainer.removeAttribute('hidden');
this.tabs.map((tab) => {
tab.accordionButton.setAttribute('hidden', '');
tab.accordionButton.setAttribute('role', 'tabpanel');
if (tab.accordionButton.getAttribute('aria-expanded') === 'true') {
tab.tab.setAttribute('active', '');
}
return tab;
});
this.setAttribute('view', 'tabs');
} else {
if (this.view === 'accordion') {
return;
}
this.tabButtonContainer.setAttribute('hidden', '');
this.tabs.map((tab) => {
tab.accordionButton.removeAttribute('hidden');
tab.accordionButton.setAttribute('role', 'region');
return tab;
});
this.setAttribute('view', 'accordion');
}
}
getStorageKey() {
return window.location.href.toString().split(window.location.host)[1].replace(/&return=[a-zA-Z0-9%]+/, '').split('#')[0];
}
saveState(value) {
const storageKey = this.getStorageKey();
sessionStorage.setItem(storageKey, value);
}
activateFromState() {
this.hasNested = this.querySelector('joomla-tab') instanceof HTMLElement;
// Use the sessionStorage state!
const href = sessionStorage.getItem(this.getStorageKey());
if (href) {
const currentTabIndex = this.tabs.findIndex((tab) => tab.tab.id === href);
if (currentTabIndex >= 0) {
this.activateTab(currentTabIndex, false);
} else if (this.hasNested) {
const childTabs = this.querySelector('joomla-tab');
if (childTabs) {
const activeTabs = [].slice.call(this.querySelectorAll('joomla-tab-element'))
.reverse()
.filter((activeTabEl) => activeTabEl.id === href);
if (activeTabs.length) {
// Activate the deepest tab
let activeTab = activeTabs[0].closest('joomla-tab');
[].slice.call(activeTab.querySelectorAll('joomla-tab-element'))
.forEach((tabEl) => {
tabEl.removeAttribute('active');
if (tabEl.id === href) {
tabEl.setAttribute('active', '');
}
});
// Activate all parent tabs
while (activeTab.parentNode.closest('joomla-tab') !== this) {
const parentTabContainer = activeTab.closest('joomla-tab');
const parentTabEl = activeTab.parentNode.closest('joomla-tab-element');
[].slice.call(parentTabContainer.querySelectorAll('joomla-tab-element'))
// eslint-disable-next-line no-loop-func
.forEach((tabEl) => {
tabEl.removeAttribute('active');
if (parentTabEl === tabEl) {
tabEl.setAttribute('active', '');
activeTab = parentTabEl;
}
});
}
[].slice.call(this.children)
.filter((el) => el.tagName.toLowerCase() === 'joomla-tab-element')
.forEach((tabEl) => {
tabEl.removeAttribute('active');
const isActiveChild = tabEl.querySelector('joomla-tab-element[active]');
if (isActiveChild) {
this.activateTab(tabEl, false);
}
});
}
}
}
}
}
/* Method to dispatch events */
dispatchCustomEvent(eventName, element, related) {
const OriginalCustomEvent = new CustomEvent(eventName, { bubbles: true, cancelable: true });
OriginalCustomEvent.relatedTarget = related;
element.dispatchEvent(OriginalCustomEvent);
}
}
customElements.define('joomla-tab', TabsElement);