UNPKG

jcore-ui

Version:

jcore-ui - components for building an interface

295 lines (229 loc) 8.67 kB
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