toggler.js
import $ from 'jquery';
import Component from '@veams/component';
import transitionEndEvent from '@veams/helpers/lib/detection/transition-end-event';
class Toggler extends Component {
$el = $(this.el);
savedStyles = this.$el.attr('style');
windowWidth = this.context.detections.width;
$toggleTabindexElems = this.options.toggleTabindexElems
? $(this.options.toggleTabindexElems, this.el)
: null;
constructor(obj) {
let options = {
classes: {
a11yFocusKey: 'a11y-focus-key',
calculating: 'is-calculating',
close: 'is-closed',
open: 'is-open'
},
context: false,
dataMaxAttr: 'data-js-height',
focusEl: null,
globalEvent: '',
globalEventId: '',
setOverflow: false,
toggleTabindexElems: ''
};
super(obj, options);
}
static get info() {
return {
version: '1.0.0',
vc: true,
mod: false
};
}
get height() {
return this._height;
}
set height(height) {
this._height = height;
}
get isOpen() {
return this._isOpen;
}
set isOpen(bool) {
this._isOpen = bool;
}
get subscribe() {
return {
'{{context.EVENTS.resize}}': 'updateHeight'
};
}
didMount() {
let selfInit =
this.$el.attr('data-js-module') &&
this.$el.attr('data-js-module').indexOf('toggler') > -1;
if (selfInit && !this.options.globalEvent) {
console.info('@veams/component-toggler :: this.options.globalEvent not set.');
}
this.isOpen = this.$el.hasClass(this.options.classes.open);
this.calculateHeight().then(() => !this.isOpen && this.setHeight(0));
}
bindEvents() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
this.updateHeight();
}
});
});
observer.observe(this.el, { childList: true });
if (this.options.globalEvent) {
this.registerEvent('{{this.options.globalEvent}}', 'toggle', true);
}
}
updateHeight() {
if (this.windowWidth === this.context.detections.width) {
return;
}
this.windowWidth = this.context.detections.width;
clearTimeout(this.updateHeightTimeout);
this.updateHeightTimeout = setTimeout(() => {
this.calculateHeight().then(() => this.isOpen && this.setHeight());
}, 200);
}
enableCalcMode() {
if (!this.isOpen) {
this.$el.addClass(this.options.classes.open);
this.$el.removeClass(this.options.classes.close);
}
this.$el.addClass(this.options.classes.calculating);
}
disableCalcMode() {
this.$el.removeClass(this.options.classes.calculating);
if (!this.isOpen) {
this.$el.addClass(this.options.classes.close);
this.$el.removeClass(this.options.classes.open);
} else {
this.setHeight();
}
}
setHeight(height) {
this.$el.css(
'height',
typeof height === 'number'
? height + 'px'
: this.$el.attr(this.options.dataMaxAttr) + 'px'
);
}
calcHeight() {
return new Promise((resolve, reject) => {
clearTimeout(this.calcHeightTimeout);
this.calcHeightTimeout = setTimeout(() => {
let wantedHeight = this.$el.outerHeight();
this.$el.attr(this.options.dataMaxAttr, wantedHeight);
this.height = wantedHeight !== this.height ? wantedHeight : this.height;
resolve();
}, 10);
});
}
calculateHeight() {
return new Promise((resolve, reject) => {
if (this.el && this.el.hasAttribute('style')) {
this.saveStyles();
}
this.enableCalcMode(true);
this.calcHeight().then(() => {
if (this.savedStyles) {
this.restoreStyles();
}
this.disableCalcMode();
resolve();
});
});
}
saveStyles() {
this.savedStyles = this.$el.attr('style');
this.$el.removeAttr('style');
}
restoreStyles() {
this.$el.attr('style', this.savedStyles);
this.savedStyles = null;
}
toggle(obj) {
if (this.options.globalEventId && obj.options && obj.options.globalEventId) {
if (this.options.globalEventId !== obj.options.globalEventId) {
return;
}
}
if (obj.isActive) {
this.open(obj);
} else {
this.close();
}
}
open(obj) {
this.$el
.css('height', this.$el.attr(this.options.dataMaxAttr) + 'px')
.attr('aria-hidden', false)
.removeClass(this.options.classes.close)
.addClass(this.options.classes.open);
this.$srcEl = obj && $(obj.el);
if (this.$toggleTabindexElems && this.$toggleTabindexElems.length) {
this.$el.on(transitionEndEvent(), () => {
this.$toggleTabindexElems.attr('tabindex', 0);
if (this.$srcEl.hasClass(this.options.classes.a11yFocusKey)) {
this.$toggleTabindexElems.eq(0).addClass(this.options.classes.a11yFocusKey);
}
this.$toggleTabindexElems[0].focus();
this.$el.off(transitionEndEvent());
});
}
this.context.Vent.trigger(this.context.EVENTS.toggler.open, {
context: this.options.context
});
if (this.options.setOverflow) {
this.$el.on(transitionEndEvent(), () => {
this.$el.css('overflow', 'visible');
this.$el.off(transitionEndEvent());
});
}
this.isOpen = true;
}
close() {
this.$el
.removeAttr('style')
.css('height', 0)
.attr('aria-hidden', true)
.removeClass(this.options.classes.open)
.addClass(this.options.classes.close);
if (this.options.setOverflow) {
this.$el.css('overflow', 'hidden');
}
if (this.$toggleTabindexElems && this.$toggleTabindexElems.length) {
this.$toggleTabindexElems.attr('tabindex', -1);
}
this.isOpen = false;
}
}
export default Toggler;