UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

385 lines (384 loc) 11.6 kB
import { DtIconClose as I } from "@dialpad/dialtone-icons/vue3"; import D from "../../common/mixins/modal.js"; import { MODAL_BANNER_KINDS as f, MODAL_SIZE_MODIFIERS as _, MODAL_KIND_MODIFIERS as y } from "./modal-constants.js"; import { disableRootScrolling as O, returnFirstEl as b, enableRootScrolling as p, hasSlotContent as T, getUniqueString as B } from "../../common/utils/index.js"; import { EVENT_KEYNAMES as n } from "../../common/constants/index.js"; import F from "../../shared/sr_only_close_button.js"; import { DialtoneLocalization as M } from "../../localization/index.js"; import { resolveComponent as d, createBlock as m, openBlock as o, mergeProps as A, toHandlers as N, withCtx as u, createElementBlock as a, createCommentVNode as g, createVNode as E, normalizeClass as r, renderSlot as c, createTextVNode as L, toDisplayString as h, Transition as v, withDirectives as z, createElementVNode as K, vShow as R } from "vue"; import { _ as q } from "../../_plugin-vue_export-helper-CHgC5LLL.js"; import j from "../button/button.js"; import H from "../lazy-show/lazy-show.js"; import { NOTICE_KINDS as V } from "../notice/notice-constants.js"; const P = { compatConfig: { MODE: 3 }, name: "DtModal", components: { DtLazyShow: H, DtButton: j, DtIconClose: I, SrOnlyCloseButton: F }, mixins: [D], props: { /** * Body text to display as the modal's main content. */ copy: { type: String, default: "" }, /** * Id to use for the dialog's aria-describedby. * Recommended only if the dialog content itself isn't enough to give full context, * as screen readers should recite the dialog contents by default before any aria-description. */ describedById: { type: String, default: "" }, /** * Id to use for the dialog's aria-labelledby. */ labelledById: { type: String, default: function() { return B(); } }, /** * Whether the modal should be shown. * Parent component can sync on this value to control the modal's visibility. * @values true, false */ show: { type: Boolean, default: !1 }, /** * Title text to display in the modal header. */ title: { type: String, default: "" }, /** * Title text to display in the modal banner. */ bannerTitle: { type: String, default: "" }, /** * The theme of the modal. kind - default or danger, * @values default, danger */ kind: { type: String, default: "default", validator: (e) => Object.keys(y).includes(e) }, /** * The size of the modal. size - default or full, * @values default, full */ size: { type: String, default: "default", validator: (e) => Object.keys(_).includes(e) }, /** * Additional class name for the root modal element. * Can accept String, Object, and Array, i.e. has the * same API as Vue's built-in handling of the class attribute. */ modalClass: { type: [String, Object, Array], default: "" }, /** * Additional class name for the dialog element within the modal. * Can accept String, Object, and Array, i.e. has the * same API as Vue's built-in handling of the class attribute. */ dialogClass: { type: [String, Object, Array], default: "" }, /** * Additional class name for the content element within the modal. * Can accept String, Object, and Array, i.e. has the * same API as Vue's built-in handling of the class attribute. */ contentClass: { type: [String, Object, Array], default: "" }, /** * Sets the color of the banner. * @values base, error, info, success, warning */ bannerKind: { type: String, default: "warning", validate(e) { return V.includes(e); } }, /** * Additional class name for the banner element within the modal. * Can accept String, Object, and Array, i.e. has the * same API as Vue's built-in handling of the class attribute. */ bannerClass: { type: [String, Object, Array], default: "" }, /** * Hides the close button on the modal * @values true, false */ hideClose: { type: Boolean, default: !1 }, /** * Whether the modal will close when you click outside of the dialog on the overlay. * @values true, false */ closeOnClick: { type: Boolean, default: !0 }, /** * Scrollable modal that allows scroll the modal content keeping the header and footer fixed * @values true, false */ fixedHeaderFooter: { type: Boolean, default: !0 }, /** * The element that is focused when the modal is opened. This can be an * HTMLElement within the modal, a string starting with '#' which will * find the element by ID. 'first' which will automatically focus * the first element, or 'dialog' which will focus the dialog window itself. * If the dialog is modal this prop cannot be 'none'. */ initialFocusElement: { type: [String, HTMLElement], default: "first", validator: (e) => e === "first" || e instanceof HTMLElement || e.startsWith("#") } }, emits: [ /** * Native button click event * * @event click * @type {PointerEvent | KeyboardEvent} */ "click", /** * Native keydown event * * @event keydown * @type {KeyboardEvent} */ "keydown", /** * The modal will emit a "false" boolean value for this event when the user performs a modal-closing action. * Parent components can sync on this value to create a 2-way binding to control modal visibility. * * @event update:show * @type {Boolean} */ "update:show" ], data() { return { MODAL_KIND_MODIFIERS: y, MODAL_SIZE_MODIFIERS: _, MODAL_BANNER_KINDS: f, EVENT_KEYNAMES: n, hasSlotContent: T, i18n: new M() }; }, computed: { modalListeners() { return { click: (e) => { this.closeOnClick && e.target === e.currentTarget ? this.close() : this.show && e.target !== e.currentTarget && this.handleModalClick(e), this.$emit("click", e); }, keydown: (e) => { switch (e.code) { case n.esc: case n.escape: this.close(); break; case n.tab: this.trapFocus(e); break; } this.$emit("keydown", e); }, "after-enter": async () => { this.$emit("update:show", !0), await this.setFocusAfterTransition(); }, focusin: (e) => { this.show && !this.$el.contains(e.target) && (e.preventDefault(), this.focusFirstElement()); } }; }, open() { return `${!this.show}`; }, hasFooterSlot() { return !!this.$slots.footer; }, bannerKindClass() { return f[this.bannerKind]; }, closeButtonTitle() { return this.i18n.$t("DIALTONE_CLOSE_BUTTON"); } }, watch: { show: { handler(e) { var s; e ? (this.previousActiveElement = document.activeElement, O(b(this.$el).getRootNode().host)) : (p(b(this.$el).getRootNode().host), (s = this.previousActiveElement) == null || s.focus(), this.previousActiveElement = null); } } }, methods: { close() { this.$emit("update:show", !1); }, async setFocusAfterTransition() { this.initialFocusElement === "first" ? await this.focusFirstElement() : this.initialFocusElement.startsWith("#") ? await this.focusElementById(this.initialFocusElement) : this.initialFocusElement instanceof HTMLElement && this.initialFocusElement.focus(); }, trapFocus(e) { this.show && this.focusTrappedTabPress(e); }, handleModalClick(e) { const s = e.target, t = this._getFocusableElements(); t.length && !t.includes(s) && (t.includes(document.activeElement) || this.focusFirstElement()); } } }, U = ["aria-describedby", "aria-labelledby"], W = ["id"], Z = ["id"], x = { key: 4, class: "d-modal__footer" }; function Y(e, s, t, G, i, l) { const S = d("sr-only-close-button"), C = d("dt-icon-close"), k = d("dt-button"), w = d("dt-lazy-show"); return o(), m(w, A({ transition: "d-zoom", show: t.show, class: [ "d-modal", i.MODAL_KIND_MODIFIERS[t.kind], i.MODAL_SIZE_MODIFIERS[t.size], t.modalClass ], "data-qa": "dt-modal", "aria-hidden": l.open }, N(l.modalListeners)), { default: u(() => [ t.show && (i.hasSlotContent(e.$slots.banner) || t.bannerTitle) ? (o(), a("div", { key: 0, "data-qa": "dt-modal-banner", class: r([ "d-modal__banner", t.bannerClass, l.bannerKindClass ]) }, [ c(e.$slots, "banner", {}, () => [ L(h(t.bannerTitle), 1) ]) ], 2)) : g("", !0), E(v, { appear: "", name: "d-modal__dialog" }, { default: u(() => [ z(K("div", { class: r([ "d-modal__dialog", { "d-modal__dialog--scrollable": t.fixedHeaderFooter }, t.dialogClass ]), role: "dialog", "aria-modal": "true", "aria-describedby": t.describedById, "aria-labelledby": t.labelledById }, [ i.hasSlotContent(e.$slots.header) ? (o(), a("div", { key: 0, id: t.labelledById, class: "d-modal__header", "data-qa": "dt-modal-title" }, [ c(e.$slots, "header") ], 8, W)) : (o(), a("h2", { key: 1, id: t.labelledById, class: "d-modal__header", "data-qa": "dt-modal-title" }, h(t.title), 9, Z)), i.hasSlotContent(e.$slots.default) ? (o(), a("div", { key: 2, class: r([ "d-modal__content", t.contentClass ]), "data-qa": "dt-modal-copy" }, [ c(e.$slots, "default") ], 2)) : (o(), a("p", { key: 3, class: r([ "d-modal__content", t.contentClass ]), "data-qa": "dt-modal-copy" }, h(t.copy), 3)), l.hasFooterSlot ? (o(), a("footer", x, [ c(e.$slots, "footer") ])) : g("", !0), t.hideClose ? (o(), m(S, { key: 5, onClose: l.close }, null, 8, ["onClose"])) : (o(), m(k, { key: 6, class: "d-modal__close", "data-qa": "dt-modal-close-button", circle: "", size: "lg", importance: "clear", "aria-label": l.closeButtonTitle, title: l.closeButtonTitle, onClick: l.close }, { icon: u(() => [ E(C, { size: "400" }) ]), _: 1 }, 8, ["aria-label", "title", "onClick"])) ], 10, U), [ [R, t.show] ]) ]), _: 3 }) ]), _: 3 }, 16, ["show", "class", "aria-hidden"]); } const de = /* @__PURE__ */ q(P, [["render", Y]]); export { de as default }; //# sourceMappingURL=modal.js.map