flyonui
Version:
The easiest, free and open-source Tailwind CSS component library with semantic classes.
220 lines (164 loc) • 6.51 kB
text/typescript
/*
* HSCollapse
* @version: 3.2.2
* @author: Preline Labs Ltd.
* @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html)
* Copyright 2024 Preline Labs Ltd.
*/
import { dispatch, afterTransition } from '../../utils'
import { ICollapse } from './interfaces'
import HSBasePlugin from '../base-plugin'
import { ICollectionItem } from '../../interfaces'
import HSDropdown from '../dropdown'
class HSCollapse extends HSBasePlugin<{}> implements ICollapse {
private readonly contentId: string | null
public content: HTMLElement | null
private animationInProcess: boolean
private onElementClickListener: () => void
constructor(el: HTMLElement, options?: {}, events?: {}) {
super(el, options, events)
this.contentId = this.el.dataset.collapse
this.content = document.querySelector(this.contentId)
this.animationInProcess = false
if (this.content) this.init()
}
private elementClick() {
if (this.content.classList.contains('open')) {
this.hide()
} else {
this.show()
}
}
private init() {
this.createCollection(window.$hsCollapseCollection, this)
this.onElementClickListener = () => this.elementClick()
if (this?.el?.ariaExpanded) {
if (this.el.classList.contains('open')) this.el.ariaExpanded = 'true'
else this.el.ariaExpanded = 'false'
}
this.el.addEventListener('click', this.onElementClickListener)
}
private hideAllMegaMenuItems() {
this.content.querySelectorAll('.mega-menu-content.block').forEach(el => {
el.classList.remove('block')
el.classList.add('hidden')
})
}
// This function is added to close all dropdowns when the collapse is closed
private closeDropdowns(): void {
if (!this.content) return
const dropdowns = this.content.querySelectorAll('.dropdown')
dropdowns.forEach((el: Element) => {
try {
const instance = HSDropdown.getInstance(el as HTMLElement, true) as ICollectionItem<HSDropdown> | null
if (!instance?.element) return
if (el instanceof HTMLElement && el.classList.contains('open')) {
instance.element.close(false)
}
} catch (error) {
console.warn('Error closing dropdown:', error)
}
})
}
// Public methods
public show() {
if (this.animationInProcess || this.el.classList.contains('open')) return false
this.animationInProcess = true
this.el.classList.add('open')
if (this?.el?.ariaExpanded) this.el.ariaExpanded = 'true'
this.content.classList.add('open')
this.content.classList.remove('hidden')
this.content.style.height = '0'
setTimeout(() => {
this.content.style.height = `${this.content.scrollHeight}px`
this.fireEvent('beforeOpen', this.el)
dispatch('beforeOpen.collapse', this.el, this.el)
})
afterTransition(this.content, () => {
this.content.style.height = ''
this.fireEvent('open', this.el)
dispatch('open.collapse', this.el, this.el)
this.animationInProcess = false
})
}
public hide() {
if (this.animationInProcess || !this.el.classList.contains('open')) return false
this.animationInProcess = true
this.el.classList.remove('open')
if (this?.el?.ariaExpanded) this.el.ariaExpanded = 'false'
this.content.style.height = `${this.content.scrollHeight}px`
setTimeout(() => {
this.content.style.height = '0'
})
this.content.classList.remove('open')
afterTransition(this.content, () => {
this.content.classList.add('hidden')
this.content.style.height = ''
this.fireEvent('hide', this.el)
dispatch('hide.collapse', this.el, this.el)
this.animationInProcess = false
})
if (this.content.querySelectorAll('.mega-menu-content.block').length) {
this.hideAllMegaMenuItems()
}
this.closeDropdowns()
}
public destroy() {
this.el.removeEventListener('click', this.onElementClickListener)
this.content = null
this.animationInProcess = false
window.$hsCollapseCollection = window.$hsCollapseCollection.filter(({ element }) => element.el !== this.el)
}
// Static methods
private static findInCollection(target: HSCollapse | HTMLElement | string): ICollectionItem<HSCollapse> | null {
return (
window.$hsCollapseCollection.find(el => {
if (target instanceof HSCollapse) return el.element.el === target.el
else if (typeof target === 'string') return el.element.el === document.querySelector(target)
else return el.element.el === target
}) || null
)
}
static getInstance(target: HTMLElement, isInstance = false) {
const elInCollection = window.$hsCollapseCollection.find(
el => el.element.el === (typeof target === 'string' ? document.querySelector(target) : target)
)
return elInCollection ? (isInstance ? elInCollection : elInCollection.element.el) : null
}
static autoInit() {
if (!window.$hsCollapseCollection) window.$hsCollapseCollection = []
if (window.$hsCollapseCollection)
window.$hsCollapseCollection = window.$hsCollapseCollection.filter(({ element }) => document.contains(element.el))
document.querySelectorAll('.collapse-toggle:not(.--prevent-on-load-init)').forEach((el: HTMLElement) => {
if (!window.$hsCollapseCollection.find(elC => (elC?.element?.el as HTMLElement) === el)) new HSCollapse(el)
})
}
static show(target: HSCollapse | HTMLElement | string) {
const instance = HSCollapse.findInCollection(target)
if (instance && instance.element.content.classList.contains('hidden')) instance.element.show()
}
static hide(target: HSCollapse | HTMLElement | string) {
const instance = HSCollapse.findInCollection(target)
if (instance && !instance.element.content.classList.contains('hidden')) instance.element.hide()
}
// Backward compatibility
static on(evt: string, target: HSCollapse | HTMLElement | string, cb: Function) {
const instance = HSCollapse.findInCollection(target)
if (instance) instance.element.events[evt] = cb
}
}
declare global {
interface Window {
HSCollapse: Function
$hsCollapseCollection: ICollectionItem<HSCollapse>[]
}
}
window.addEventListener('load', () => {
HSCollapse.autoInit()
// Uncomment for debug
// console.log('Collapse collection:', window.$hsCollapseCollection);
})
if (typeof window !== 'undefined') {
window.HSCollapse = HSCollapse
}
export default HSCollapse