jcore-ui
Version:
jcore-ui - components for building an interface
295 lines (229 loc) • 8.67 kB
text/typescript
import Component, { IComponent, IComponentOn, IComponentOptions, IComponentSelector } from "../core/Component";
import config from '../../config';
import { IObject } from "../interfaces";
import { $, JDom } from "../core/JDom";
const selectors = {
element: `.${config.prefix}-accordion`,
panel: `.${config.prefix}-accordion-panel`,
tab: `.${config.prefix}-accordion-tab`,
content: `.${config.prefix}-accordion-content`
}
interface IAccordionSelector extends IComponentSelector {
panel?: string;
tab?: string;
content?: string;
}
interface IAccordionOptions extends IComponentOptions {
active?: boolean;
multiple?: boolean;
duration?: number;
}
interface IAccordionOn extends IComponentOn {
toggle?: (ctx?: Component) => void;
}
interface IAccordion extends IComponent {
selectors?: IAccordionSelector;
options?: IAccordionOptions;
on?: IAccordionOn;
}
interface Accordion {
options: IAccordionOptions;
$tabs: JDom;
selectors: IAccordionSelector;
on: IAccordionOn;
id: number;
isShow: boolean;
isShowTabs: Array<{
tab: any;
id: number;
isShow: boolean;
}>;
$tab: JDom;
}
class Accordion extends Component {
constructor(props: IAccordion = {}) {
super({
...props,
Component: Accordion,
selectors: Object.assign(selectors, props.selectors || {})
});
if (this.options && this.options.mount) {
this.mount();
}
}
mount() {
if (!this.$element || this._mount || this.$element.hasAttr('data-mount')) {
return;
};
super.mount();
this.options = Object.assign({
active: this.$element.dataset.active || false,
multiple: this.$element.dataset.multiple || false,
duration: this.$element.dataset.duration || 300
}, this.options);
this.on = Object.assign({
toggle: () => { }
}, this.on);
this.handlers = {
// change: this.change.bind(this),
resize: this.resize.bind(this),
toggle: this.toggle.bind(this)
};
this.id = 0;
this.$tab = null;
this.isShow = false;
this.isShowTabs = [];
this.$tabs = $(this.$element).find(this.selectors.tab);
this.addEvents();
this.on.mount(this);
this.emitter.emit('mount', this);
}
addEvents() {
this.$tabs.element.forEach((tab, i) => {
const $tab = $(tab);
const $panel = $tab.closest(this.selectors.panel);
const $content = $panel.find(this.selectors.content);
const id = this.setId($tab);
$content.style.setProperty('transition', `height ease-in-out ${this.options.duration}ms`);
this.isShowTabs.push({ tab, id, isShow: false });
if (this.options.active && i === 0) {
this.toggle($tab, $content);
}
$tab.on('click', this.handlers.toggle);
})
window.addEventListener('resize', this.handlers.resize);
}
unmount() {
super.unmount();
this.$tabs.element.forEach(tab => $(tab).off('click', this.handlers.toggle, null));
window.removeEventListener('resize', this.handlers.resize);
this.on.unmount(this);
this.emitter.emit('unmount', this);
}
toggle(e: Event | JDom, cont?: JDom) {
const tab = $((e instanceof HTMLElement ? e : e instanceof JDom ? e : e.target as unknown as JDom)).closest(this.selectors.tab);
const panel = tab.closest(this.selectors.panel);
const content = cont || panel.find(this.selectors.content);
// Если последний активный таб не равен текущему, сбрасываем состояние
if (this.$tab?.dataset.id !== tab.dataset.id && !this.options.multiple && this.$tabs.element.length > 1) {
this.isShow = false;
// если в аккордеоне несколько панелей, скрываем все, кроме текущей
this.hideAll(tab);
}
// Если multiple не вклчюен, то у всех общее состояние
if (!this.options.multiple) {
if (this.isShow) {
this.hide(tab, content);
} else {
this.show(tab, content);
}
this.isShow = !this.isShow;
} else {
const tabParam = this.isShowTabs.filter(param => param.tab.dataset.id === tab.dataset.id)[0];
if (tabParam.isShow) {
this.setShowTab(tab, false);
this.hide(tab, content);
} else {
this.setShowTab(tab, true);
this.show(tab, content);
}
this.isShow = !this.isShow;
}
this.on.toggle(this);
this.emitter.emit('toggle', this);
}
setId(tab: JDom) {
tab.attr('data-id', `${this.id++}`);
return this.id;
}
show(tab: JDom, content: JDom) {
const height = content.scrollHeight;
this.$tab = tab;
$(this.$element).attr('data-active', '');
tab.attr('data-active', '');
content.addClass('is-showing', 'is-active');
this.setHeight(`${height}px`, content);
setTimeout(() => {
this.showEnd(content);
}, this.options.duration);
}
showEnd(content: JDom) {
content.removeClass('is-showing');
this.emitter.emit('show', this);
// content.style.setProperty('height', `auto`)
}
hide(tab: JDom, content: JDom) {
tab.attr('data-active', null);
content.removeClass('is-active');
content.addClass('is-hiding');
const active = this.isShowTabs.filter(param => param.isShow).length;
/** Если нет активного таба, то вырубаем основной элемент */
if (!active) {
$(this.$element).attr('data-active', null);
}
this.setHeight(0, content);
setTimeout(() => {
this.hideEnd(content);
}, this.options.duration);
}
hideEnd(content: JDom) {
// content.removeAttr('show');
// content.removeAttr('hiding');
content.removeClass('is-hiding');
this.emitter.emit('hide', this);
}
hideAll(currentTab: JDom) {
if (currentTab) {
const needTabsHidden = this.$tabs.element.filter(tab => tab !== currentTab.get());
needTabsHidden.forEach(tab => {
const panel = $(tab).closest(this.selectors.panel);
const content = panel.find(this.selectors.content);
// content.removeAttr('showing');
// content.removeAttr('show');
// content.removeAttr('hiding');
$(tab).attr('data-active', null);
this.setHeight(0, content);
});
this.$tab = currentTab;
} else {
this.$tabs.element.forEach(tab => {
const panel = $(tab).closest(this.selectors.panel);
const content = panel.find(this.selectors.content);
// content.removeAttr('showing');
// content.removeAttr('show');
// content.removeAttr('hiding');
$(tab).attr('data-active', null);
this.setHeight(0, content);
});
this.$tab = null;
}
}
setHeight(height: string | number | HTMLElement, content: JDom) {
content.style.setProperty('height', `${content.scrollHeight}px`);
window.requestAnimationFrame(() => {
content.style.setProperty('height', `${height}`);
});
}
setShowTab(tab: JDom, state: boolean) {
const needTab = this.isShowTabs.filter(param => param.tab === tab.get())[0];
this.isShowTabs = this.isShowTabs.map(param => {
if (param.tab === needTab.tab) {
param.isShow = state;
}
return param;
});
}
resize() {
this.$tabs.element.forEach((tab, i) => {
const $tab = $(tab);
if (!$tab.hasAttr('data-active')) {
return;
}
const panel = $tab.closest(this.selectors.panel);
const content = panel.find(this.selectors.content);
content.style.setProperty('height', `auto`);
this.setHeight(tab, content);
});
}
}
export default Accordion