UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

393 lines (392 loc) 11.8 kB
import { DtIconClose as I } from "@dialpad/dialtone-icons/vue3"; import v from "../../common/mixins/modal.js"; import { MODAL_KIND_MODIFIERS as y, MODAL_SIZE_MODIFIERS as _, MODAL_BANNER_KINDS as b } from "./modal-constants.js"; import { getUniqueString as O, hasSlotContent as B, disableRootScrolling as D, returnFirstEl as C, enableRootScrolling as F } from "../../common/utils/index.js"; import { EVENT_KEYNAMES as i } from "../../common/constants/index.js"; import M from "../../common/mixins/sr-only-close-button.js"; import A from "../../shared/sr_only_close_button.js"; import { resolveComponent as n, openBlock as o, createBlock as m, mergeProps as S, toHandlers as T, withCtx as h, createElementBlock as a, normalizeClass as d, renderSlot as r, createTextVNode as L, toDisplayString as f, createCommentVNode as c, createVNode as g, Transition as N, withDirectives as H, createElementVNode as z, vShow as K } from "vue"; import { _ as R } from "../../_plugin-vue_export-helper-CHgC5LLL.js"; import q from "../lazy-show/lazy-show.js"; import j from "../button/button.js"; import { NOTICE_KINDS as P } from "../notice/notice-constants.js"; const V = { compatConfig: { MODE: 3 }, name: "DtModal", components: { DtLazyShow: q, DtButton: j, DtIconClose: I, SrOnlyCloseButton: A }, mixins: [v, M], props: { /** * A set of props to be passed into the modal's close button. * Requires an 'ariaLabel' property. */ closeButtonProps: { type: Object, required: !0, validator: (e) => !!e.ariaLabel }, /** * 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 O(); } }, /** * 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 P.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: b, EVENT_KEYNAMES: i, hasSlotContent: B }; }, computed: { modalListeners() { return { click: (e) => { this.closeOnClick && (e.target === e.currentTarget && this.close(), this.$emit("click", e)); }, keydown: (e) => { switch (e.code) { case i.esc: case i.escape: this.close(); break; case i.tab: this.trapFocus(e); break; } this.$emit("keydown", e); }, "after-enter": (e) => { this.$emit("update:show", !0), e.target === e.currentTarget && this.setFocusAfterTransition(); } }; }, open() { return `${!this.show}`; }, hasFooterSlot() { return !!this.$slots.footer; }, bannerKindClass() { return b[this.bannerKind]; } }, watch: { show: { handler(e) { var u; e ? (this.previousActiveElement = document.activeElement, D(C(this.$el).getRootNode().host)) : (F(C(this.$el).getRootNode().host), (u = this.previousActiveElement) == null || u.focus(), this.previousActiveElement = null); } }, $props: { immediate: !0, deep: !0, handler() { this.validateProps(); } } }, methods: { close() { this.$emit("update:show", !1); }, setFocusAfterTransition() { this.initialFocusElement === "first" ? this.focusFirstElement() : this.initialFocusElement.startsWith("#") ? this.focusElementById(this.initialFocusElement) : this.initialFocusElement instanceof HTMLElement && this.initialFocusElement.focus(); }, trapFocus(e) { this.show && this.focusTrappedTabPress(e); }, validateProps() { this.hideClose && !this.visuallyHiddenClose && console.error(`If hideClose prop is true, visuallyHiddenClose and visuallyHiddenCloseLabel props need to be set so the component always includes a close button`); } } }, W = ["aria-describedby", "aria-labelledby"], Z = ["id"], U = ["id"], Y = { key: 4, class: "d-modal__footer" }; function x(e, u, t, G, s, l) { const p = n("dt-icon-close"), E = n("dt-button"), k = n("sr-only-close-button"), w = n("dt-lazy-show"); return o(), m(w, S({ transition: "d-zoom", show: t.show, class: [ "d-modal", s.MODAL_KIND_MODIFIERS[t.kind], s.MODAL_SIZE_MODIFIERS[t.size], t.modalClass ], "data-qa": "dt-modal", "aria-hidden": l.open }, T(l.modalListeners)), { default: h(() => [ t.show && (s.hasSlotContent(e.$slots.banner) || t.bannerTitle) ? (o(), a("div", { key: 0, "data-qa": "dt-modal-banner", class: d([ "d-modal__banner", t.bannerClass, l.bannerKindClass ]) }, [ r(e.$slots, "banner", {}, () => [ L(f(t.bannerTitle), 1) ]) ], 2)) : c("", !0), g(N, { appear: "", name: "d-modal__dialog" }, { default: h(() => [ H(z("div", { class: d([ "d-modal__dialog", { "d-modal__dialog--scrollable": t.fixedHeaderFooter }, t.dialogClass ]), role: "dialog", "aria-modal": "true", "aria-describedby": t.describedById, "aria-labelledby": t.labelledById }, [ s.hasSlotContent(e.$slots.header) ? (o(), a("div", { key: 0, id: t.labelledById, class: "d-modal__header", "data-qa": "dt-modal-title" }, [ r(e.$slots, "header") ], 8, Z)) : (o(), a("h2", { key: 1, id: t.labelledById, class: "d-modal__header", "data-qa": "dt-modal-title" }, f(t.title), 9, U)), s.hasSlotContent(e.$slots.default) ? (o(), a("div", { key: 2, class: d([ "d-modal__content", t.contentClass ]), "data-qa": "dt-modal-copy" }, [ r(e.$slots, "default") ], 2)) : (o(), a("p", { key: 3, class: d([ "d-modal__content", t.contentClass ]), "data-qa": "dt-modal-copy" }, f(t.copy), 3)), l.hasFooterSlot ? (o(), a("footer", Y, [ r(e.$slots, "footer") ])) : c("", !0), t.hideClose ? c("", !0) : (o(), m(E, S({ key: 5, class: "d-modal__close", circle: "", size: "lg", importance: "clear", "aria-label": t.closeButtonProps.ariaLabel }, t.closeButtonProps, { onClick: l.close }), { icon: h(() => [ g(p, { size: "400" }) ]), _: 1 }, 16, ["aria-label", "onClick"])), e.showVisuallyHiddenClose ? (o(), m(k, { key: 6, "visually-hidden-close-label": e.visuallyHiddenCloseLabel, onClose: l.close }, null, 8, ["visually-hidden-close-label", "onClose"])) : c("", !0) ], 10, W), [ [K, t.show] ]) ]), _: 3 }) ]), _: 3 }, 16, ["show", "class", "aria-hidden"]); } const de = /* @__PURE__ */ R(V, [["render", x]]); export { de as default }; //# sourceMappingURL=modal.js.map