formstone
Version:
Library of modular front end components.
362 lines (275 loc) • 7.83 kB
JavaScript
import MediaQuery from './mediaquery.js';
import Swap from './swap.js';
import {
extend,
//
element,
select,
iterate,
//
on,
off,
//
addClass,
removeClass,
//
getAttr,
setAttr,
removeAttr,
} from './utils.js';
// Class
class Tabs {
static #_guid = 1;
static #_defaults = {
maxWidth: Infinity,
mobileMaxWidth: '980px'
};
//
static defaults(options) {
this.#_defaults = extend(true, this.#_defaults, options);
}
static construct(selector, options) {
let targets = select(selector);
targets.forEach((el) => {
if (!el.Tabs) {
new Tabs(el, options);
}
});
return targets;
}
//
constructor(el, options) {
if (el.Tabs) {
console.warn('Tabs: Instance already exists', el);
return;
}
// Parse JSON Options
let optionsData = {};
let dataset = el.dataset;
try {
optionsData = JSON.parse(dataset.tabsOptions || '{}');
} catch (e) {
console.warn('Tabs: Error parsing options JSON', el);
}
// Internal Data
Object.assign(this, extend(true, this.constructor.#_defaults, options || {}, optionsData));
this.el = el;
this.guid = this.constructor.#_guid++;
this.guidClass = `fs-tabs-element-${this.guid}`;
this.guidContent = `fs-tabs-content-${this.guid}`;
let mobileMaxWidth = (this.mobileMaxWidth === Infinity ? '100000px' : this.mobileMaxWidth);
this.mq = `(max-width: ${mobileMaxWidth})`;
this.target = el.hash;
this.container = dataset.tabsContainer;
this.group = dataset.tabsGroup;
this.mobileEl = element('button');
this.contentEl = select(this.target)[0];
this.containerEl = select(this.container)[0];
this.handleEl = [el, this.mobileEl];
//
this.mobileEl.innerHTML = el.innerHTML;
setAttr(this.mobileEl, {
'type': 'button',
'aria-hidden': 'true',
'tabindex': '-1',
});
addClass(el, 'fs-tabs-tab', this.guidClass);
addClass(this.mobileEl, 'fs-tabs-tab_mobile', this.guidClass);
addClass(this.contentEl, 'fs-tabs-content', this.guidContent);
addClass(this.containerEl, 'fs-tabs-container');
this.contentEl.parentNode.insertBefore(this.mobileEl, this.contentEl);
// Aria
// SHOULD BE IN ENABLE / DISABLE?
this.originalId = getAttr(el, 'id');
if (this.originalId) {
this.elId = this.originalId;
} else {
this.elId = this.guidClass;
setAttr(el, 'id', this.elId);
}
this.originalContentId = getAttr(el, 'id');
if (this.originalContentId) {
this.contentId = this.originalContentId;
} else {
this.contentId = this.guidContent;
setAttr(this.contentEl, 'id', this.contentId);
}
// Check for hash
let hash = window.location.hash;
this.hashActive = false;
this.hashGroup = false;
if (hash.length) {
this.hashActive = (el.hash == hash);
this.hashGroup = this.group && (select(`[data-tabs-group="${this.group}"][href$="${hash}]`).length > 0);
}
// SHOULD BE IN ENABLE / DISABLE?
if (this.hashActive) {
setAttr(this.handleEl, 'data-swap-active', 'true');
} else if (this.hashGroup) {
removeAttr(this.handleEl, 'data-swap-active');
} else if (this.el.dataset.tabsActive === 'true') {
setAttr(this.handleEl, 'data-swap-active', 'true');
}
//
this.listeners = {
activate: this.#onActivate(this),
// deactivate: this.#onDeactivate(this),
enable: this.#onEnable(this),
disable: this.#onDisable(this)
};
this.handleEl.forEach((handle) => {
setAttr(handle, {
'data-swap-target': `.${this.guidContent}`,
'data-swap-linked': `${this.guidClass}`,
'data-swap-group': `${this.group}`
});
on(handle, 'swap:activate', this.listeners.activate);
// on(handle, 'swap:deactivate', this.listeners.deactivate);
on(handle, 'swap:enable', this.listeners.enable);
on(handle, 'swap:disable', this.listeners.disable);
});
MediaQuery.bind(this.guidClass, this.mq, {
enter: () => {
console.log('enter');
this.#mobileEnable();
},
leave: () => {
console.log('leave');
this.#mobileDisable();
}
});
Swap.construct(`.${this.guidClass}`, {
classes: {
enabled: 'fs-tabs-enabled',
active: 'fs-tabs-active',
inactive: 'fs-tabs-inactive',
},
collapse: false,
maxWidth: this.maxWidth
});
el.Tabs = this;
}
//
destroy() {
this.listeners.disable.call();
removeClass(this.el, 'fs-tabs-tab', this.guidClass);
removeClass(this.contentEl, 'fs-tabs-content', this.guidContent);
removeClass(this.containerEl, 'fs-tabs-container');
removeAttr(this.handle, [
'data-swap-target',
'data-swap-linked',
'data-swap-group'
]);
this.handleEl.forEach((handle) => {
off(handle, 'swap:activate', this.listeners.activate);
// off(handle, 'swap:deactivate', this.listeners.deactivate);
off(handle, 'swap:enable', this.listeners.enable);
off(handle, 'swap:disable', this.listeners.disable);
handle.Swap.destroy();
});
this.mobileEl.remove();
this.el.Tabs = null;
delete this.el.Tabs;
}
//
enable() {
this.el.Swap.enable();
}
disable() {
this.el.Swap.disable();
}
//
activate() {
this.el.Swap.activate();
}
deactivate() {
this.el.Swap.deactivate();
}
//
#onEnable() {
return (e) => {
setAttr(this.el, {
'role': 'tab',
'aria-controls': this.contentId
});
setAttr(this.contentEl, {
'role': 'tabpanel',
'aria-labelledby': this.elId
});
setAttr(this.containerEl, {
'role': 'tablist'
});
addClass(this.contentEl, 'fs-tabs-enabled');
addClass(this.contentEl, 'fs-tabs-enabled');
// setAttr(this.el, 'aria-label', this.label);
// if (!this.isToggle) {
// setAttr(this.el, {
// 'role': 'dialog',
// 'aria-modal': 'true'
// });
// }
// setAttr(this.handle, 'aria-controls', this.elId);
// if (this.isToggle) {
// setAttr(this.handle, 'aria-expanded', 'false');
// }
// addClass(this.content, 'fs-navigation-enabled');
// setTimeout(() => {
// addClass(this.el, 'fs-navigation-animated');
// addClass(this.content, 'fs-navigation-animated');
// }, 0);
};
}
#onDisable() {
return (e) => {
removeAttr(this.el, 'role', 'aria-controls');
removeAttr(this.contentEl, 'role', 'aria-labelledby');
removeAttr(this.containerEl, 'role');
removeClass(this.contentEl, 'fs-tabs-enabled');
// SHOULD BE IN ENABLE / DISABLE
// setAttr(this.el, {
// 'role': this.originalRole || false,
// 'aria-label': this.originaLabel || false,
// 'aria-modal': this.originaModal || false,
// 'id': this.originalId || false
// });
};
}
#onActivate() {
return (e) => {
// window.location.hash = this.el.hash;
};
}
#onDeactivate() {
// return (e) => {
// };
}
//
#mobileEnable() {
iterate(this.handleEl, (handle) => {
addClass(handle, 'fs-tabs-mobile');
});
setAttr(this.el, {
'aria-hidden': 'true',
'tabindex': '-1',
});
removeAttr(this.mobileEl, [
'aria-hidden',
'tabindex'
]);
}
#mobileDisable() {
iterate(this.handleEl, (handle) => {
removeClass(handle, 'fs-tabs-mobile');
});
setAttr(this.mobileEl, {
'aria-hidden': 'true',
'tabindex': '-1'
});
removeAttr(this.el, [
'aria-hidden',
'tabindex'
]);
}
};
// Export
export default Tabs;