@dialpad/dialtone-vue
Version:
Vue component library for Dialpad's design system Dialtone
311 lines (310 loc) • 10.6 kB
JavaScript
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