preline
Version:
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
1,058 lines (868 loc) • 28 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-hs-overlay="#${this.el.id}"]`),
);
const toggleDataOptions = this.collectToggleParameters(this.toggleButtons);
this.toggleMinifierButtons = Array.from(
document.querySelectorAll(`[data-hs-overlay-minifier="#${this.el.id}"]`),
);
const data = el.getAttribute("data-hs-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 ??
"hs-overlay-backdrop transition duration fixed inset-0 bg-gray-900/50 dark:bg-neutral-900/80";
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-hs-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(".hs-overlay-animation-target") || this.el;
this.initialZIndex = parseInt(getComputedStyle(this.el).zIndex, 10);
this.onElementClickListener = [];
this.onElementMinifierClickListener = [];
this.initiallyOpened = document.body.classList.contains(
"hs-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.hs.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.hs.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("hs-overlay-minified");
if (cb) cb();
} else {
this.el.classList.remove("minified");
document.body.classList.remove("hs-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-hs-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("hs-overlay-backdrop-open:") ||
value.includes(":hs-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-hs-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-hs-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(".hs-overlay.open");
const currentlyOpened = window.$hsOverlayCollection.find(
(el) =>
Array.from(openedOverlays).includes(el.element.el) &&
!el.element.isLayoutAffect,
);
const toggles = document.querySelectorAll(
`[data-hs-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("hs-overlay-body-open");
}
if (!this.initiallyOpened) {
this.el.focus();
this.el.style.outline = "none";
}
this.initiallyOpened = false;
this.fireEvent("open", this.el);
dispatch("open.hs.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("hs-overlay-body-open");
}
const closeFn = (_cb: Function) => {
if (this.el.classList.contains("open")) return false;
const toggles = document.querySelectorAll(
`[data-hs-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.hs.overlay", this.el, this.el);
if (window.HSAccessibilityObserver && this.accessibilityComponent) {
window.HSAccessibilityObserver.updateComponentState(
this.accessibilityComponent,
false,
);
}
if (!document.querySelector(".hs-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("hs-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-hs-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("hs-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-hs-overlay")
? _temp.getAttribute("data-hs-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(".hs-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("hs-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",
".hs-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-hs-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("hs-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("hs-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("hs-overlay-body-open");
} else {
document.body.classList.remove("hs-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;