UNPKG

@sandlada/vue-mdc

Version:

![Vue MDC Logo](https://raw.githubusercontent.com/sandlada/vue-mdc/refs/heads/main/docs/vue-mdc-cover.png)

317 lines (316 loc) 10.6 kB
/** * @license * Copyright 2025 Sandlada & Kai Orion * SPDX-License-Identifier: MIT */ import { defineComponent, createVNode } from "vue"; import { isServer } from "../../utils/is-server.js"; import { redispatchEvent } from "../../internals/events/redispatch-event.js"; import "../../internals/navigation/render-navigation-destination.js"; import { componentNamePrefix } from "../../internals/component-name-prefix/component-name-prefix.js"; import { Divider } from "../divider/divider.js"; import "../divider/divider.definition.js"; import { DialogDefaultCloseAnimation, DialogDefaultOpenAnimation } from "./animations.js"; import { SDialogController } from "./dialog-controller.js"; import { props } from "./dialog.definition.js"; import css from "./styles/dialog.module.scss.js"; const Dialog = /* @__PURE__ */ defineComponent({ name: `${componentNamePrefix}-dialog`, props, slots: {}, emits: ["open", "opend", "cancel", "close", "closed", "submit", "update:modelValue"], created() { if (this.modelValue !== null) { this.$emit("update:modelValue", this.defaultOpen); } }, mounted() { this.isOpen = this.defaultOpen; this.$el[SDialogController] = { show: () => { this.isOpen = true; }, close: () => { this.isOpen = true; } }; if (isServer()) { return; } this.intersectionObserver = new IntersectionObserver((entries) => { for (const entry of entries) { this.handleAnchorIntersection(entry); } }, { root: this.getScrollerElement() }); this.intersectionObserver.observe(this.getTopAnchorElement()); this.intersectionObserver.observe(this.getBottomAnchorElement()); this.getContainerElement().addEventListener("click", this.handleContentClick); this.$el.addEventListener("submit", this.handleSubmit); this.getDialogElement().addEventListener("click", this.handleDialogClick); this.getDialogElement().addEventListener("cancel", this.handleCancel); this.getDialogElement().addEventListener("close", this.handleClose); this.getDialogElement().addEventListener("keydown", this.handleKeydown); this.getDialogElement().returnValue = this.returnValue; }, updated() { if (this.modelValue !== null) { this.isOpen = this.modelValue; } this.getDialogElement().returnValue = this.returnValue; }, data: () => ({ animationController: null, open: false, opening: false, returnValue: "", nextClickIsFromContent: false, escapePressedWithoutCancel: false, isAtScrollTop: false, isAtScrollBottom: false, intersectionObserver: null }), computed: { isOpen: { get() { return this.open; }, set(value) { if (this.open === value) { return; } this.open = value; if (this.isOpen) { this.show(); } else { this.close(); } } } }, methods: { async show() { this.opening = true; if (this.getDialogElement().open || !this.opening) { this.opening = false; return; } const openEvent = new Event("open", { bubbles: false, cancelable: true, composed: false }); this.$emit("open", openEvent); const preventOpen = !this.$el.dispatchEvent(openEvent); if (preventOpen) { this.isOpen = false; this.opening = false; return; } this.isOpen = true; this.getDialogElement().showModal(); await this.animateDialog(DialogDefaultOpenAnimation); this.opening = false; const opendEvent = new Event("opend", { bubbles: false, cancelable: false, composed: false }); this.$emit("opend", opendEvent); this.$el.dispatchEvent(opendEvent); }, async close(returnValue) { this.opening = false; if (!this.getDialogElement().open || this.opening) { this.isOpen = false; return; } const preReturnValue = this.returnValue; this.returnValue = returnValue ? returnValue : this.returnValue; const closeEvent = new Event("close", { bubbles: false, cancelable: true, composed: false }); this.$emit("close", closeEvent); const preventClose = !this.$el.dispatchEvent(closeEvent); if (preventClose) { this.returnValue = preReturnValue; return; } await this.animateDialog(DialogDefaultCloseAnimation); this.getDialogElement().close(returnValue ? returnValue : this.returnValue); this.isOpen = false; const closedEvent = new Event("closed"); this.$emit("closed", closedEvent); this.$el.dispatchEvent(closeEvent); }, getDialogElement() { return this.$el.querySelector("&>dialog"); }, getScrimElement() { return this.$el.querySelector(`&>.${css.scrim}`); }, getContentElement() { return this.$el.querySelector(`& .${css.content}`); }, getContainerElement() { return this.$el.querySelector(`& .${css.container}`); }, getScrollerElement() { return this.$el.querySelector(`& .${css.scroller}`); }, getHeadlineElement() { return this.$el.querySelector(`& .${css.headline}`); }, getActionsElement() { return this.$el.querySelector(`& .${css.actions}`); }, getTopAnchorElement() { return this.$el.querySelector(`& .${css.top}.${css.anchor}`); }, getBottomAnchorElement() { return this.$el.querySelector(`& .${css.bottom}.${css.anchor}`); }, async animateDialog(animation) { this.animationController?.abort(); this.animationController = new AbortController(); const { container: containerAnimate, dialog: dialogAnimate, scrim: scrimAnimate, headline: headlineAnimate, content: contentAnimate, actions: actionsAnimate } = animation; const elementAndAnimation = [[this.getDialogElement(), dialogAnimate ?? []], [this.getScrimElement(), scrimAnimate ?? []], [this.getContainerElement(), containerAnimate ?? []], [this.getHeadlineElement(), headlineAnimate ?? []], [this.getContentElement(), contentAnimate ?? []], [this.getActionsElement(), actionsAnimate ?? []]]; const animations = []; for (const [element, animation2] of elementAndAnimation) { for (const animateArgs of animation2) { const animation3 = element.animate(...animateArgs); this.animationController.signal.addEventListener("abort", () => { animation3.cancel(); }); animations.push(animation3); } } await Promise.all(animations.map((animation2) => animation2.finished.catch(() => { }))); }, handleCancel(e) { if (e.target !== this.getDialogElement()) { return; } this.escapePressedWithoutCancel = false; this.$emit("cancel", e); const preventCancel = !redispatchEvent(this.$el, e); e.preventDefault(); if (preventCancel) { return; } this.close(); }, handleClose() { if (!this.escapePressedWithoutCancel) { return; } this.escapePressedWithoutCancel = false; this.getDialogElement().dispatchEvent(new Event("cancel", { cancelable: true })); }, handleSubmit(e) { const form = e.target; const { submitter } = e; if (form.method !== "dialog" || !submitter) { return; } this.close(submitter.getAttribute("value") ?? this.returnValue); }, handleContentClick() { this.nextClickIsFromContent = true; }, handleDialogClick(e) { if (this.nextClickIsFromContent) { this.nextClickIsFromContent = false; return; } const cancelEvent = new Event("cancel"); this.$emit("cancel", cancelEvent); const preventCancel = !this.$el.dispatchEvent(cancelEvent); if (preventCancel) { return; } this.close(); }, handleKeydown(event) { if (event.key !== "Escape") { return; } this.escapePressedWithoutCancel = true; setTimeout(() => { this.escapePressedWithoutCancel = false; }); }, handleAnchorIntersection(entry) { const { target, isIntersecting } = entry; if (target === this.getTopAnchorElement()) { this.isAtScrollTop = isIntersecting; } if (target === this.getBottomAnchorElement()) { this.isAtScrollBottom = isIntersecting; } } }, render() { const scrollable = this.isOpen && !(this.isAtScrollTop && this.isAtScrollBottom); const classes = [this.$slots.headline && css["has-headline"], this.$slots.actions && css["has-actions"], this.$slots.icon && css["has-icon"], scrollable && css.scrollable, scrollable && !this.isAtScrollTop && css["show-top-divider"], scrollable && !this.isAtScrollBottom && css["show-bottom-divider"]]; const renderHeadline = createVNode("div", { "class": css["headline-wrapper"] }, [createVNode("div", { "class": css["icon-wrapper"], "aria-hidden": "true" }, [this.$slots.icon && createVNode("span", { "class": css.icon }, [this.$slots.icon()])]), createVNode("h2", { "class": css.headline }, [this.$slots.headline && this.$slots.headline()]), createVNode(Divider, null, null)]); const renderScroller = createVNode("div", { "class": css.scroller }, [createVNode("div", { "class": css["content-wrapper"] }, [createVNode("div", { "class": [css.top, css.anchor] }, null), this.$slots.default && createVNode("span", { "class": css.content }, [this.$slots.default()]), createVNode("div", { "class": [css.bottom, css.anchor] }, null)])]); const renderActions = createVNode("div", { "class": css["actions-wrapper"] }, [createVNode(Divider, null, null), this.$slots.actions && createVNode("span", { "class": css.actions }, [this.$slots.actions()])]); return createVNode("div", { "data-component": "dialog", "class": [css["dialog-container"], ...classes] }, [createVNode("div", { "class": css.scrim }, null), createVNode("dialog", { "role": "alertdialog" }, [createVNode("div", { "class": css.container }, [renderHeadline, renderScroller, renderActions])])]); }, inheritAttrs: true }); export { Dialog }; //# sourceMappingURL=dialog.js.map