flyonui
Version:
The easiest, free and open-source Tailwind CSS component library with semantic classes.
900 lines (711 loc) • 28.5 kB
text/typescript
/*
* HSOverlay
* @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 { afterTransition, dispatch, getClassProperty, isDirectChild, stringToBoolean } from '../../utils'
import { IOverlay, IOverlayOptions } from './interfaces'
import { TOverlayOptionsAutoCloseEqualityType } from './types'
import { ICollectionItem } from '../../interfaces'
import { IAccessibilityComponent } from '../accessibility-manager/interfaces'
import { BREAKPOINTS } from '../../constants'
import HSBasePlugin from '../base-plugin'
import HSAccessibilityObserver from '../accessibility-manager'
class HSOverlay extends HSBasePlugin<{}> implements IOverlay {
private accessibilityComponent: IAccessibilityComponent
private lastFocusedToggle: HTMLElement | null = null
private initiallyOpened: boolean
private readonly hiddenClass: string | null
private readonly emulateScrollbarSpace: boolean
private readonly isClosePrev: boolean
private readonly backdropClasses: string | null
private readonly backdropParent: string | HTMLElement | Document
private readonly backdropExtraClasses: string | null
private readonly animationTarget: HTMLElement | null
private openNextOverlay: boolean
private autoHide: ReturnType<typeof setTimeout> | null
private toggleButtons: HTMLElement[]
public toggleMinifierButtons: HTMLElement[]
static openedItemsQty = 0
public initContainer: HTMLElement | null
public isCloseWhenClickInside: boolean
public isTabAccessibilityLimited: boolean
public isLayoutAffect: boolean
public hasAutofocus: boolean
public hasDynamicZIndex: boolean
public hasAbilityToCloseOnBackdropClick: boolean
public openedBreakpoint: number | null
public autoClose: number | null
public autoCloseEqualityType: TOverlayOptionsAutoCloseEqualityType | null
public moveOverlayToBody: number | null
private backdrop: HTMLElement | null
private initialZIndex = 0
static currentZIndex = 0
private onElementClickListener:
| {
el: HTMLElement
fn: () => void
}[]
| null
private onElementMinifierClickListener:
| {
el: HTMLElement
fn: () => void
}[]
| null
private onOverlayClickListener: (evt: Event) => void
private onBackdropClickListener: () => void
constructor(el: HTMLElement, options?: IOverlayOptions, events?: {}) {
super(el, options, events)
// Collect all data options from toggles
this.toggleButtons = Array.from(document.querySelectorAll(`[data-overlay="#${this.el.id}"]`))
const toggleDataOptions = this.collectToggleParameters(this.toggleButtons)
this.toggleMinifierButtons = Array.from(document.querySelectorAll(`[data-overlay-minifier="#${this.el.id}"]`))
const data = el.getAttribute('data-overlay-options')
const dataOptions: IOverlayOptions = data ? JSON.parse(data) : {}
const concatOptions = {
...dataOptions,
...toggleDataOptions,
...options
}
this.hiddenClass = concatOptions?.hiddenClass || 'hidden'
this.emulateScrollbarSpace = concatOptions?.emulateScrollbarSpace || false
this.isClosePrev = concatOptions?.isClosePrev ?? true
this.backdropClasses =
concatOptions?.backdropClasses ??
'overlay-backdrop transition duration-300 fixed inset-0 bg-base-300/60 overflow-y-auto'
this.backdropParent =
typeof concatOptions.backdropParent === 'string'
? (document.querySelector(concatOptions.backdropParent) as HTMLElement)
: document.body
this.backdropExtraClasses = concatOptions?.backdropExtraClasses ?? ''
this.moveOverlayToBody = concatOptions?.moveOverlayToBody || null
this.openNextOverlay = false
this.autoHide = null
this.initContainer = this.el?.parentElement || null
this.isCloseWhenClickInside = stringToBoolean(
getClassProperty(this.el, '--close-when-click-inside', 'false') || 'false'
)
this.isTabAccessibilityLimited = stringToBoolean(
getClassProperty(this.el, '--tab-accessibility-limited', 'true') || 'true'
)
this.isLayoutAffect = stringToBoolean(getClassProperty(this.el, '--is-layout-affect', 'false') || 'false')
this.hasAutofocus = stringToBoolean(getClassProperty(this.el, '--has-autofocus', 'true') || 'true')
this.hasDynamicZIndex = stringToBoolean(getClassProperty(this.el, '--has-dynamic-z-index', 'false') || 'false')
this.hasAbilityToCloseOnBackdropClick = stringToBoolean(this.el.getAttribute('data-overlay-keyboard') || 'true')
const autoCloseBreakpoint = getClassProperty(this.el, '--auto-close')
const autoCloseEqualityType = getClassProperty(this.el, '--auto-close-equality-type')
const openedBreakpoint = getClassProperty(this.el, '--opened')
this.autoClose =
!isNaN(+autoCloseBreakpoint) && isFinite(+autoCloseBreakpoint)
? +autoCloseBreakpoint
: BREAKPOINTS[autoCloseBreakpoint] || null
this.autoCloseEqualityType = (autoCloseEqualityType as TOverlayOptionsAutoCloseEqualityType) ?? null
this.openedBreakpoint =
(!isNaN(+openedBreakpoint) && isFinite(+openedBreakpoint) ? +openedBreakpoint : BREAKPOINTS[openedBreakpoint]) ||
null
this.animationTarget = this?.el?.querySelector('.overlay-animation-target') || this.el
this.initialZIndex = parseInt(getComputedStyle(this.el).zIndex, 10)
this.onElementClickListener = []
this.onElementMinifierClickListener = []
this.initiallyOpened = document.body.classList.contains('overlay-body-open')
this.init()
}
private elementClick() {
const payloadFn = () => {
const payload = {
el: this.el,
isOpened: !!this.el.classList.contains('open')
}
this.fireEvent('toggleClicked', payload)
dispatch('toggleClicked.overlay', this.el, payload)
}
if (this.el.classList.contains('opened')) this.close(false, payloadFn)
else this.open(payloadFn)
}
private elementMinifierClick() {
const payloadFn = () => {
const payload = {
el: this.el,
isMinified: !!this.el.classList.contains('minified')
}
this.fireEvent('toggleMinifierClicked', payload)
dispatch('toggleMinifierClicked.overlay', this.el, payload)
}
if (this.el.classList.contains('minified')) this.minify(false, payloadFn)
else this.minify(true, payloadFn)
}
public minify(isMinified: boolean, cb: Function | null = null) {
if (isMinified) {
this.el.classList.add('minified')
document.body.classList.add('overlay-minified')
if (cb) cb()
} else {
this.el.classList.remove('minified')
document.body.classList.remove('overlay-minified')
if (cb) cb()
}
}
private overlayClick(evt: Event) {
if (
(evt.target as HTMLElement).id &&
`#${(evt.target as HTMLElement).id}` === this.el.id &&
this.isCloseWhenClickInside &&
this.hasAbilityToCloseOnBackdropClick
) {
this.close()
}
}
private backdropClick() {
this.close()
}
private init() {
this.createCollection(window.$hsOverlayCollection, this)
if (this.isLayoutAffect && this.openedBreakpoint) {
const instance = HSOverlay.getInstance(this.el, true)
HSOverlay.setOpened(this.openedBreakpoint, instance as ICollectionItem<HSOverlay>)
}
this.onOverlayClickListener = evt => this.overlayClick(evt)
this.el.addEventListener('click', this.onOverlayClickListener)
if (this.toggleButtons.length) this.buildToggleButtons(this.toggleButtons)
if (this.toggleMinifierButtons.length) this.buildToggleMinifierButtons()
if (typeof window !== 'undefined') {
if (!window.HSAccessibilityObserver) {
window.HSAccessibilityObserver = new HSAccessibilityObserver()
}
this.setupAccessibility()
}
}
private buildToggleButtons(buttons: HTMLElement[]) {
buttons.forEach(el => {
if (this.el.classList.contains('opened')) el.ariaExpanded = 'true'
else el.ariaExpanded = 'false'
this.onElementClickListener.push({
el,
fn: () => this.elementClick()
})
el.addEventListener('click', this.onElementClickListener.find(toggleButton => toggleButton.el === el).fn)
})
}
private buildToggleMinifierButtons() {
this.toggleMinifierButtons.forEach(el => {
if (this.el.classList.contains('minified')) el.ariaExpanded = 'true'
else el.ariaExpanded = 'false'
this.onElementMinifierClickListener.push({
el,
fn: () => this.elementMinifierClick()
})
el.addEventListener(
'click',
this.onElementMinifierClickListener.find(minifierButton => minifierButton.el === el).fn
)
})
}
private hideAuto() {
const time = parseInt(getClassProperty(this.el, '--auto-hide', '0'))
if (time) {
this.autoHide = setTimeout(() => {
this.close()
}, time)
}
}
private checkTimer() {
if (this.autoHide) {
clearTimeout(this.autoHide)
this.autoHide = null
}
}
private buildBackdrop() {
const overlayClasses = this.el.classList.value.split(' ')
const overlayZIndex = parseInt(window.getComputedStyle(this.el).getPropertyValue('z-index'))
const backdropId = this.el.getAttribute('data-overlay-backdrop-container') || false
this.backdrop = document.createElement('div')
let backdropClasses = `${this.backdropClasses} ${this.backdropExtraClasses}`
const closeOnBackdrop = getClassProperty(this.el, '--overlay-backdrop', 'true') !== 'static'
const disableBackdrop = getClassProperty(this.el, '--overlay-backdrop', 'true') === 'false'
this.backdrop.id = `${this.el.id}-backdrop`
if ('style' in this.backdrop) {
this.backdrop.style.zIndex = `${overlayZIndex - 1}`
}
for (const value of overlayClasses) {
if (value.startsWith('overlay-backdrop-open:') || value.includes(':overlay-backdrop-open:')) {
backdropClasses += ` ${value}`
}
}
if (disableBackdrop) return
if (backdropId) {
this.backdrop = document.querySelector(backdropId).cloneNode(true) as HTMLElement
this.backdrop.classList.remove('hidden')
backdropClasses = `${(this.backdrop as HTMLElement).classList.toString()}`
this.backdrop.classList.value = ''
}
if (closeOnBackdrop) {
this.onBackdropClickListener = () => this.backdropClick()
this.backdrop.addEventListener('click', this.onBackdropClickListener, true)
}
this.backdrop.setAttribute('data-overlay-backdrop-template', '')
;(this.backdropParent as HTMLElement).appendChild(this.backdrop)
setTimeout(() => {
this.backdrop.classList.value = backdropClasses
})
}
private destroyBackdrop() {
const backdrop: HTMLElement = document.querySelector(`#${this.el.id}-backdrop`)
if (!backdrop) return
if (this.openNextOverlay) {
backdrop.style.transitionDuration = `${
parseFloat(window.getComputedStyle(backdrop).transitionDuration.replace(/[^\d.-]/g, '')) * 1.8
}s`
}
backdrop.classList.add('opacity-0')
afterTransition(backdrop, () => {
backdrop.remove()
})
}
private focusElement() {
const input: HTMLInputElement = this.el.querySelector('[autofocus]')
if (!input) return false
else input.focus()
}
private getScrollbarSize() {
let div = document.createElement('div')
div.style.overflow = 'scroll'
div.style.width = '100px'
div.style.height = '100px'
document.body.appendChild(div)
let scrollbarSize = div.offsetWidth - div.clientWidth
document.body.removeChild(div)
return scrollbarSize
}
private collectToggleParameters(buttons: HTMLElement[]) {
let toggleData = {}
buttons.forEach(el => {
const data = el.getAttribute('data-overlay-options')
const dataOptions: IOverlayOptions = data ? JSON.parse(data) : {}
toggleData = {
...toggleData,
...dataOptions
}
})
return toggleData
}
private isElementVisible(): boolean {
const style = window.getComputedStyle(this.el)
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
return false
}
const rect = this.el.getBoundingClientRect()
if (rect.width === 0 || rect.height === 0) {
return false
}
let parent = this.el.parentElement
while (parent) {
const parentStyle = window.getComputedStyle(parent)
if (parentStyle.display === 'none' || parentStyle.visibility === 'hidden' || parentStyle.opacity === '0') {
return false
}
parent = parent.parentElement
}
return true
}
private isOpened(): boolean {
return this.el.classList.contains('open') && !this.el.classList.contains(this.hiddenClass)
}
// Public methods
public open(cb: Function | null = null) {
if (this.el.classList.contains('minified')) {
this.minify(false)
}
if (this.hasDynamicZIndex) {
if (HSOverlay.currentZIndex < this.initialZIndex) {
HSOverlay.currentZIndex = this.initialZIndex
}
HSOverlay.currentZIndex++
this.el.style.zIndex = `${HSOverlay.currentZIndex}`
}
const openedOverlays = document.querySelectorAll('.overlay.open')
const currentlyOpened = window.$hsOverlayCollection.find(
el => Array.from(openedOverlays).includes(el.element.el) && !el.element.isLayoutAffect
)
const toggles = document.querySelectorAll(`[data-overlay="#${this.el.id}"]`)
const disabledScroll = getClassProperty(this.el, '--body-scroll', 'false') !== 'true'
this.lastFocusedToggle = document.activeElement as HTMLElement
if (this.isClosePrev && currentlyOpened) {
this.openNextOverlay = true
return currentlyOpened.element.close().then(() => {
this.open()
this.openNextOverlay = false
})
}
if (disabledScroll) {
document.body.style.overflow = 'hidden'
if (this.emulateScrollbarSpace) {
document.body.style.paddingRight = `${this.getScrollbarSize()}px`
}
}
this.buildBackdrop()
this.checkTimer()
this.hideAuto()
toggles.forEach(toggle => {
if (toggle.ariaExpanded) toggle.ariaExpanded = 'true'
})
this.el.classList.remove(this.hiddenClass)
this.el.setAttribute('aria-overlay', 'true')
this.el.setAttribute('tabindex', '-1')
setTimeout(() => {
if (this.el.classList.contains('opened')) return false
this.el.classList.add('open', 'opened')
if (this.isLayoutAffect) {
document.body.classList.add('overlay-body-open')
}
if (!this.initiallyOpened) {
this.el.focus()
this.el.style.outline = 'none'
}
this.initiallyOpened = false
this.fireEvent('open', this.el)
dispatch('open.overlay', this.el, this.el)
if (window.HSAccessibilityObserver && this.accessibilityComponent) {
window.HSAccessibilityObserver.updateComponentState(this.accessibilityComponent, true)
}
if (this.hasAutofocus) this.focusElement()
if (typeof cb === 'function') cb()
if (this.isElementVisible()) HSOverlay.openedItemsQty++
}, 50)
}
public close(forceClose = false, cb: Function | null = null) {
if (this.isElementVisible()) {
HSOverlay.openedItemsQty = HSOverlay.openedItemsQty <= 0 ? 0 : HSOverlay.openedItemsQty - 1
}
if (HSOverlay.openedItemsQty === 0 && this.isLayoutAffect) {
document.body.classList.remove('overlay-body-open')
}
const closeFn = (_cb: Function) => {
if (this.el.classList.contains('open')) return false
const toggles = document.querySelectorAll(`[data-overlay="#${this.el.id}"]`)
toggles.forEach(toggle => {
if (toggle.ariaExpanded) toggle.ariaExpanded = 'false'
})
this.el.classList.add(this.hiddenClass)
if (this.hasDynamicZIndex) this.el.style.zIndex = ''
this.destroyBackdrop()
this.fireEvent('close', this.el)
dispatch('close.overlay', this.el, this.el)
if (window.HSAccessibilityObserver && this.accessibilityComponent) {
window.HSAccessibilityObserver.updateComponentState(this.accessibilityComponent, false)
}
if (!document.querySelector('.overlay.opened')) {
document.body.style.overflow = ''
if (this.emulateScrollbarSpace) document.body.style.paddingRight = ''
}
if (this.lastFocusedToggle) {
this.lastFocusedToggle.focus()
this.lastFocusedToggle = null
}
_cb(this.el)
if (typeof cb === 'function') cb()
if (HSOverlay.openedItemsQty === 0) {
document.body.classList.remove('overlay-body-open')
if (this.hasDynamicZIndex) HSOverlay.currentZIndex = 0
}
}
return new Promise(resolve => {
this.el.classList.remove('open', 'opened')
this.el.removeAttribute('aria-overlay')
this.el.removeAttribute('tabindex')
this.el.style.outline = ''
if (forceClose) closeFn(resolve)
else afterTransition(this.animationTarget, () => closeFn(resolve))
})
}
public updateToggles() {
const found = Array.from(document.querySelectorAll<HTMLElement>(`[data-overlay="#${this.el.id}"]`))
const newButtons = found.filter(btn => !this.toggleButtons.includes(btn))
if (newButtons.length) {
this.toggleButtons.push(...newButtons)
this.buildToggleButtons(newButtons)
}
this.toggleButtons = this.toggleButtons.filter(btn => {
if (document.contains(btn)) return true
const listener = this.onElementClickListener?.find(lst => lst.el === btn)
if (listener) btn.removeEventListener('click', listener.fn as () => void)
return false
})
}
public destroy() {
// Remove classes
this.el.classList.remove('open', 'opened', this.hiddenClass)
if (this.isLayoutAffect) {
document.body.classList.remove('overlay-body-open')
}
// Remove listeners
this.el.removeEventListener('click', this.onOverlayClickListener)
if (this.onElementClickListener.length) {
this.onElementClickListener.forEach(({ el, fn }) => {
el.removeEventListener('click', fn)
})
this.onElementClickListener = null
}
if (this.backdrop) {
this.backdrop.removeEventListener('click', this.onBackdropClickListener)
}
if (this.backdrop) {
this.backdrop.remove()
this.backdrop = null
}
window.$hsOverlayCollection = window.$hsOverlayCollection.filter(({ element }) => element.el !== this.el)
}
// Static methods
private static findInCollection(target: HSOverlay | HTMLElement | string): ICollectionItem<HSOverlay> | null {
return (
window.$hsOverlayCollection.find(el => {
if (target instanceof HSOverlay) 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 | string, isInstance?: boolean) {
// Backward compatibility
const _temp = typeof target === 'string' ? document.querySelector(target) : target
const _target = _temp?.getAttribute('data-overlay') ? _temp.getAttribute('data-overlay') : target
const elInCollection = window.$hsOverlayCollection.find(
el =>
el.element.el === (typeof _target === 'string' ? document.querySelector(_target) : _target) ||
el.element.el === (typeof _target === 'string' ? document.querySelector(_target) : _target)
)
return elInCollection ? (isInstance ? elInCollection : elInCollection.element.el) : null
}
static autoInit() {
if (!window.$hsOverlayCollection) {
window.$hsOverlayCollection = []
}
if (window.$hsOverlayCollection) {
window.$hsOverlayCollection = window.$hsOverlayCollection.filter(({ element }) => document.contains(element.el))
}
document.querySelectorAll('.overlay:not(.--prevent-on-load-init)').forEach((el: HTMLElement) => {
if (!window.$hsOverlayCollection.find(elC => (elC?.element?.el as HTMLElement) === el)) {
new HSOverlay(el)
}
})
}
static open(target: HSOverlay | HTMLElement | string) {
const instance = HSOverlay.findInCollection(target)
if (instance && instance.element.el.classList.contains(instance.element.hiddenClass)) instance.element.open()
}
static close(target: HSOverlay | HTMLElement | string) {
const instance = HSOverlay.findInCollection(target)
if (instance && !instance.element.el.classList.contains(instance.element.hiddenClass)) instance.element.close()
}
static minify(target: HSOverlay | HTMLElement | string, isMinified: boolean) {
const instance = HSOverlay.findInCollection(target)
if (instance) {
instance.element.minify(isMinified)
}
}
static setOpened(breakpoint: number, el: ICollectionItem<HSOverlay>) {
if (document.body.clientWidth >= breakpoint) {
if (el.element.el.classList.contains('minified')) {
el.element.minify(false)
}
document.body.classList.add('overlay-body-open')
el.element.open()
} else el.element.close(true)
}
// Accessibility methods
private setupAccessibility(): void {
this.accessibilityComponent = window.HSAccessibilityObserver.registerComponent(
this.el,
{
onEnter: () => {
if (!this.isOpened()) this.open()
},
onEsc: () => {
if (this.isOpened()) {
this.close()
}
},
onTab: () => {
if (!this.isOpened() || !this.isTabAccessibilityLimited) return
const focusableElements = Array.from(
this.el.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
).filter(el => !el.hidden && window.getComputedStyle(el).display !== 'none')
if (focusableElements.length === 0) return
const focusedElement = this.el.querySelector(':focus')
const currentIndex = focusedElement ? focusableElements.indexOf(focusedElement as HTMLElement) : -1
const isShiftPressed = window.event instanceof KeyboardEvent && window.event.shiftKey
if (isShiftPressed) {
if (currentIndex <= 0) {
focusableElements[focusableElements.length - 1].focus()
} else {
focusableElements[currentIndex - 1].focus()
}
} else {
if (currentIndex === focusableElements.length - 1) {
focusableElements[0].focus()
} else {
focusableElements[currentIndex + 1].focus()
}
}
window.event?.preventDefault()
}
},
this.isOpened(),
'Overlay',
'.overlay'
)
this.toggleButtons.forEach(toggleButton => {
window.HSAccessibilityObserver.registerComponent(
toggleButton,
{
onEnter: () => {
if (!this.isOpened()) this.open()
},
onEsc: () => {
if (this.isOpened()) {
this.close()
}
}
},
this.isOpened(),
'Overlay Toggle',
`[data-overlay="#${this.el.id}"]`
)
})
}
// Backward compatibility
static on(evt: string, target: HSOverlay | HTMLElement | string, cb: Function) {
const instance = HSOverlay.findInCollection(target)
if (instance) instance.element.events[evt] = cb
}
}
declare global {
interface Window {
HSOverlay: Function
$hsOverlayCollection: ICollectionItem<HSOverlay>[]
}
}
let resizeTimeout: ReturnType<typeof setTimeout> | null = null
const debounceResize = (callback: () => void, delay: number = 150) => {
if (resizeTimeout) clearTimeout(resizeTimeout)
resizeTimeout = setTimeout(callback, delay)
}
const autoCloseResizeFn = () => {
if (!window.$hsOverlayCollection.length || !window.$hsOverlayCollection.find(el => el.element.autoClose)) {
return false
}
const overlays = window.$hsOverlayCollection.filter(el => el.element.autoClose)
overlays.forEach(overlay => {
const { autoCloseEqualityType, autoClose } = overlay.element
const condition =
autoCloseEqualityType === 'less-than'
? document.body.clientWidth <= autoClose
: document.body.clientWidth >= autoClose
if (condition && overlay.element.el.classList.contains('opened')) {
if (overlay.element.el.classList.contains('minified')) {
overlay.element.minify(false)
}
overlay.element.close(true)
} else {
if (overlay.element.isLayoutAffect && overlay.element.el.classList.contains('opened')) {
document.body.classList.add('overlay-body-open')
}
}
})
}
const moveOverlayToBodyResizeFn = () => {
if (!window.$hsOverlayCollection.length || !window.$hsOverlayCollection.find(el => el.element.moveOverlayToBody)) {
return false
}
const overlays = window.$hsOverlayCollection.filter(el => el.element.moveOverlayToBody)
overlays.forEach(overlay => {
const resolution = overlay.element.moveOverlayToBody
const initPlace = overlay.element.initContainer
const newPlace = document.querySelector('body')
const target = overlay.element.el
if (!initPlace && target) return false
if (document.body.clientWidth <= resolution && !isDirectChild(newPlace, target)) {
newPlace.appendChild(target)
} else if (document.body.clientWidth > resolution && !initPlace.contains(target)) {
initPlace.appendChild(target)
}
})
}
const setOpenedResizeFn = () => {
if (!window.$hsOverlayCollection.length || !window.$hsOverlayCollection.find(el => el.element.openedBreakpoint)) {
return false
}
const overlays = window.$hsOverlayCollection.filter(el => el.element.openedBreakpoint)
overlays.forEach(overlay => {
const { openedBreakpoint } = overlay.element
const condition = document.body.clientWidth >= openedBreakpoint
if (condition) {
if (!overlay.element.el.classList.contains('opened')) {
HSOverlay.setOpened(openedBreakpoint, overlay)
}
} else {
if (overlay.element.el.classList.contains('opened')) {
if (overlay.element.el.classList.contains('minified')) {
overlay.element.minify(false)
}
overlay.element.close(true)
}
}
})
}
const setBackdropZIndexResizeFn = () => {
if (
!window.$hsOverlayCollection.length ||
!window.$hsOverlayCollection.find(el => el.element.el.classList.contains('opened'))
) {
return false
}
const overlays = window.$hsOverlayCollection.filter(el => el.element.el.classList.contains('opened'))
overlays.forEach(overlay => {
const overlayZIndex = parseInt(window.getComputedStyle(overlay.element.el).getPropertyValue('z-index'))
const backdrop: HTMLElement = document.querySelector(`#${overlay.element.el.id}-backdrop`)
if (!backdrop) return false
const backdropZIndex = parseInt(window.getComputedStyle(backdrop).getPropertyValue('z-index'))
if (overlayZIndex === backdropZIndex + 1) return false
if ('style' in backdrop) backdrop.style.zIndex = `${overlayZIndex - 1}`
document.body.classList.add('overlay-body-open')
})
}
const ensureBodyOpenForMinifiedSidebar = () => {
if (!window.$hsOverlayCollection?.length) return
window.$hsOverlayCollection.forEach(overlayItem => {
const overlay = overlayItem.element
if (overlay.toggleMinifierButtons?.length > 0 && overlay.openedBreakpoint) {
if (document.body.clientWidth >= overlay.openedBreakpoint) {
document.body.classList.add('overlay-body-open')
} else {
document.body.classList.remove('overlay-body-open')
}
}
})
}
window.addEventListener('load', () => {
HSOverlay.autoInit()
moveOverlayToBodyResizeFn()
ensureBodyOpenForMinifiedSidebar()
// Uncomment for debug
// console.log('Overlay collection:', window.$hsOverlayCollection);
})
window.addEventListener('resize', () => {
debounceResize(() => {
autoCloseResizeFn()
setOpenedResizeFn()
})
moveOverlayToBodyResizeFn()
setBackdropZIndexResizeFn()
ensureBodyOpenForMinifiedSidebar()
})
if (typeof window !== 'undefined') {
window.HSOverlay = HSOverlay
}
export default HSOverlay