melt
Version:
The next generation of Melt UI. Built for Svelte 5.
94 lines (93 loc) • 2.93 kB
JavaScript
import { tick } from "svelte";
import { addEventListener } from "./event";
import { isHtmlElement } from "./is";
/**
* For some god-forsaken stupid reason, hidePopover throws an error
* if the element is already hidden, and then breaks the entire app.
* This avoids that.
*/
export function safelyHidePopover(el) {
try {
el.hidePopover();
}
catch {
// do nothing as god intended
}
}
/**
* showPopover is equally stupid.
*/
export function safelyShowPopover(el) {
try {
el.showPopover();
}
catch {
// do nothing as god intended
}
}
export function* getParentPopoversAndDialogs(element) {
let current = element;
while (current) {
// Check if current element is a dialog or has a popover
if (current instanceof HTMLDialogElement || current.hasAttribute("popover")) {
yield current;
}
// Move up the DOM tree using closest() with a parent selector
current = current.closest(":scope > *, :host > *")?.parentElement ?? null;
}
}
export function areAllParentPopoversAndDialogsOpen(element) {
for (const parent of getParentPopoversAndDialogs(element)) {
if (parent instanceof HTMLDialogElement) {
if (!parent.open)
return false;
}
else if (!parent.matches(":popover-open")) {
return false;
}
}
return true;
}
export function isDialogOrPopoverOpen(element) {
return element instanceof HTMLDialogElement ? element.open : element.matches(":popover-open");
}
export function autoOpenPopover({ el }) {
const events = [];
// Check if there's a parent popover or dialog. If so, only open if the parent's open.
// This is to guarantee correct layering.
const parent = isHtmlElement(el.parentNode)
? el.parentNode.closest("dialog, [popover]")
: undefined;
if (!isHtmlElement(parent)) {
safelyShowPopover(el);
return;
}
tick().then(() => {
// No return here, because we still wanna close it when the parents close
if (isDialogOrPopoverOpen(parent)) {
safelyShowPopover(el);
}
if (parent instanceof HTMLDialogElement) {
events.push(addEventListener(parent, "close", () => {
safelyHidePopover(el);
}), addEventListener(parent, "open", () => {
safelyShowPopover(el);
}));
}
else {
events.push(addEventListener(parent, "toggle", async (e) => {
await new Promise((r) => setTimeout(r));
const isOpen = e.newState === "open";
if (isOpen) {
safelyShowPopover(el);
}
else {
safelyHidePopover(el);
}
}));
}
});
return () => {
events.forEach((e) => e());
};
}