UNPKG

@dialpad/dialtone-vue

Version:

Vue component library for Dialpad's design system Dialtone

311 lines (310 loc) 10.6 kB
import { DtIconClose as d } from "@dialpad/dialtone-icons/vue2"; import c from "../../common/mixins/modal.js"; import { MODAL_BANNER_KINDS as i, MODAL_SIZE_MODIFIERS as n, MODAL_KIND_MODIFIERS as r } from "./modal-constants.js"; import { disableRootScrolling as m, enableRootScrolling as u, getUniqueString as f } from "../../common/utils/index.js"; import { EVENT_KEYNAMES as l } from "../../common/constants/index.js"; import h from "../../shared/sr_only_close_button.js"; import { DialtoneLocalization as p } from "../../localization/index.js"; import { Portal as _ } from "../../node_modules/@linusborg/vue-simple-portal.js"; import { n as y } from "../../_plugin-vue2_normalizer-DSLOjnn3.js"; import b from "../button/button.js"; import E from "../lazy-show/lazy-show.js"; import { NOTICE_KINDS as g } from "../notice/notice-constants.js"; const S = { name: "DtModal", components: { DtLazyShow: E, DtButton: b, DtIconClose: d, SrOnlyCloseButton: h, Portal: _ }, mixins: [c], 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 f(); } }, /** * 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(r).includes(e) }, /** * The size of the modal. size - default or full, * @values default, full */ size: { type: String, default: "default", validator: (e) => Object.keys(n).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 g.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("#") }, /** * A CSS selector string for the element to portal the modal to. If not provided, the modal will be rendered in its default location. */ appendTo: { type: String, default: void 0 } }, emits: [ /** * 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: r, MODAL_SIZE_MODIFIERS: n, MODAL_BANNER_KINDS: i, EVENT_KEYNAMES: l, i18n: new p() }; }, computed: { modalListeners() { return { ...this.$listeners, 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 l.esc: case l.escape: this.close(); break; case l.tab: this.trapFocus(e); break; } this.$emit("keydown", e); }, "after-enter": async () => { this.$emit("update:show", !0), await this.setFocusAfterTransition(); } }; }, open() { return `${!this.show}`; }, hasFooterSlot() { return !!this.$slots.footer; }, bannerKindClass() { return i[this.bannerKind]; }, closeButtonTitle() { return this.i18n.$t("DIALTONE_CLOSE_BUTTON"); } }, watch: { show: { handler(e) { var t, a, o; if (e) { this.previousActiveElement = document.activeElement; const s = ((t = this.$refs.modalRoot) == null ? void 0 : t.$el) || this.$el; m(s.getRootNode().host); } else { const s = ((a = this.$refs.modalRoot) == null ? void 0 : a.$el) || this.$el; u(s.getRootNode().host), (o = this.previousActiveElement) == null || o.focus(), this.previousActiveElement = null; } } } }, methods: { close() { this.$emit("update:show", !1); }, async setFocusAfterTransition() { var t; const e = ((t = this.$refs.modalRoot) == null ? void 0 : t.$el) || this.$el; this.initialFocusElement === "first" ? await this.focusFirstElement(e) : this.initialFocusElement.startsWith("#") ? await this.focusElementById(this.initialFocusElement) : this.initialFocusElement instanceof HTMLElement && this.initialFocusElement.focus(); }, trapFocus(e) { var t; if (this.show) { const a = ((t = this.$refs.modalRoot) == null ? void 0 : t.$el) || this.$el; this.focusTrappedTabPress(e, a); } }, handleModalClick(e) { var s; const t = e.target, a = ((s = this.$refs.modalRoot) == null ? void 0 : s.$el) || this.$el, o = this._getFocusableElements(a); o.length && !o.includes(t) && (o.includes(document.activeElement) || this.focusFirstElement(a)); } } }; var w = function() { var t = this, a = t._self._c; return a("portal", { attrs: { disabled: !t.appendTo, selector: t.appendTo } }, [a("dt-lazy-show", t._g({ ref: "modalRoot", class: [ "d-modal", t.MODAL_KIND_MODIFIERS[t.kind], t.MODAL_SIZE_MODIFIERS[t.size], t.modalClass ], attrs: { transition: "d-zoom", show: t.show, "data-qa": "dt-modal", "aria-hidden": t.open } }, t.modalListeners), [t.show && (t.$slots.banner || t.bannerTitle) ? a("div", { class: [ "d-modal__banner", t.bannerClass, t.bannerKindClass ], attrs: { "data-qa": "dt-modal-banner" } }, [t._t("banner", function() { return [t._v(" " + t._s(t.bannerTitle) + " ")]; })], 2) : t._e(), a("transition", { attrs: { appear: "", name: "d-modal__dialog" } }, [a("div", { directives: [{ name: "show", rawName: "v-show", value: t.show, expression: "show" }], class: [ "d-modal__dialog", { "d-modal__dialog--scrollable": t.fixedHeaderFooter }, t.dialogClass ], attrs: { role: "dialog", "aria-modal": "true", "aria-describedby": t.describedById, "aria-labelledby": t.labelledById } }, [t.$slots.header ? a("div", { staticClass: "d-modal__header", attrs: { id: t.labelledById, "data-qa": "dt-modal-title" } }, [t._t("header")], 2) : a("h2", { staticClass: "d-modal__header", attrs: { id: t.labelledById, "data-qa": "dt-modal-title" } }, [t._v(" " + t._s(t.title) + " ")]), t.$slots.default ? a("div", { class: [ "d-modal__content", t.contentClass ], attrs: { "data-qa": "dt-modal-copy" } }, [t._t("default")], 2) : a("p", { class: [ "d-modal__content", t.contentClass ], attrs: { "data-qa": "dt-modal-copy" } }, [t._v(" " + t._s(t.copy) + " ")]), t.hasFooterSlot ? a("footer", { staticClass: "d-modal__footer" }, [t._t("footer")], 2) : t._e(), t.hideClose ? a("sr-only-close-button", { on: { close: t.close } }) : a("dt-button", { staticClass: "d-modal__close", attrs: { "data-qa": "dt-modal-close-button", size: "md", kind: "muted", importance: "clear", "aria-label": t.closeButtonTitle, title: t.closeButtonTitle }, on: { click: t.close }, scopedSlots: t._u([{ key: "icon", fn: function({ iconSize: o }) { return [a("dt-icon-close", { attrs: { size: o } })]; } }]) })], 1)])], 1)], 1); }, C = [], $ = /* @__PURE__ */ y( S, w, C ); const N = $.exports; export { N as default }; //# sourceMappingURL=modal.js.map