import { render } from "/js/lib/lit-html.js"; import { scrollLocks } from "/js/scrollLocks.js"; import { closeWithAnimation } from "/js/dialogHelpers.js"; import { enableDragToDismiss } from "/js/dragHelpers.js"; export class Modal { static async open(...args) { const instance = new this(...args); return instance.open(); } constructor(options = {}) { this.options = options; } get className() { return "bottom-sheet text-modal"; } get attributes() { return {}; } get dragToDismiss() { return true; } canDismiss() { return true; } ignoreTouchTarget(element) { return element.closest("button") !== null; } get scrollContainerSelector() { return null; } get closing() { if (!this.dialog) return false; return !this.dialog.open || this.dialog.hasAttribute("data-closing"); } render() { throw new Error(`${this.constructor.name} must implement render()`); } open() { return new Promise((resolve) => { const dialog = document.createElement("dialog"); dialog.className = this.className; for (const [key, value] of Object.entries(this.attributes)) { dialog.setAttribute(key, value); } this.dialog = dialog; let scrollLock = null; let resolved = false; let dismissValue; let cleanedUp = false; const cleanup = () => { if (cleanedUp) return; cleanedUp = true; scrollLock?.release(); dialog.remove(); resolve(dismissValue); }; const dismiss = (value) => { if (resolved) return; resolved = true; dismissValue = value; return closeWithAnimation(dialog); }; this.dismiss = dismiss; const dismissIfAllowed = () => { if (this.canDismiss()) dismiss(); }; const update = () => { render(this.render({ dismiss, update, props: this.options }), dialog); }; update(); dialog.addEventListener("click", (event) => { if (event.target === event.currentTarget) dismissIfAllowed(); }); document.body.appendChild(dialog); scrollLock = scrollLocks.acquire({ target: dialog }); dialog.showModal(); dialog.addEventListener("close", cleanup); dialog.addEventListener("cancel", (event) => { event.preventDefault(); dismissIfAllowed(); }); if (this.dragToDismiss) { const selector = this.scrollContainerSelector; enableDragToDismiss(dialog, { onDismiss: () => dismiss(), confirmDismiss: () => this.canDismiss(), ignoreTouchTarget: (element) => this.ignoreTouchTarget(element), scrollContainer: selector ? dialog.querySelector(selector) : null, disableWhenKeyboardOpen: true, }); } }); } }