@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
424 lines (423 loc) • 14 kB
JavaScript
;
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const vue3 = require("@dialpad/dialtone-icons/vue3");
const modal$1 = require("../../common/mixins/modal.cjs");
const modal_constants = require("./modal_constants.cjs");
const common_utils = require("../../common/utils.cjs");
const common_constants = require("../../common/constants.cjs");
const sr_only_close_button$1 = require("../../common/mixins/sr_only_close_button.cjs");
const sr_only_close_button = require("../../common/sr_only_close_button.vue.cjs");
const vue = require("vue");
const _pluginVue_exportHelper = require("../../_virtual/_plugin-vue_export-helper.cjs");
const lazy_show = require("../lazy_show/lazy_show.vue.cjs");
const button = require("../button/button.vue.cjs");
const notice_constants = require("../notice/notice_constants.cjs");
const _sfc_main = {
compatConfig: { MODE: 3 },
name: "DtModal",
components: {
DtLazyShow: lazy_show.default,
DtButton: button.default,
DtIconClose: vue3.DtIconClose,
SrOnlyCloseButton: sr_only_close_button.default
},
mixins: [modal$1.default, sr_only_close_button$1.default],
props: {
/**
* A set of props to be passed into the modal's close button.
* Requires an 'ariaLabel' property.
*/
closeButtonProps: {
type: Object,
required: true,
validator: (props) => {
return !!props.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 common_utils.getUniqueString();
}
},
/**
* 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: false
},
/**
* 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: (k) => Object.keys(modal_constants.MODAL_KIND_MODIFIERS).includes(k)
},
/**
* The size of the modal. size - default or full,
* @values default, full
*/
size: {
type: String,
default: "default",
validator: (s) => Object.keys(modal_constants.MODAL_SIZE_MODIFIERS).includes(s)
},
/**
* 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(kind) {
return notice_constants.NOTICE_KINDS.includes(kind);
}
},
/**
* 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: false
},
/**
* Whether the modal will close when you click outside of the dialog on the overlay.
* @values true, false
*/
closeOnClick: {
type: Boolean,
default: true
},
/**
* Scrollable modal that allows scroll the modal content keeping the header and footer fixed
* @values true, false
*/
fixedHeaderFooter: {
type: Boolean,
default: true
},
/**
* 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: (initialFocusElement) => {
return initialFocusElement === "first" || initialFocusElement instanceof HTMLElement || initialFocusElement.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: modal_constants.MODAL_KIND_MODIFIERS,
MODAL_SIZE_MODIFIERS: modal_constants.MODAL_SIZE_MODIFIERS,
MODAL_BANNER_KINDS: modal_constants.MODAL_BANNER_KINDS,
EVENT_KEYNAMES: common_constants.EVENT_KEYNAMES,
hasSlotContent: common_utils.hasSlotContent
};
},
computed: {
modalListeners() {
return {
click: (event) => {
if (!this.closeOnClick) return;
event.target === event.currentTarget && this.close();
this.$emit("click", event);
},
keydown: (event) => {
switch (event.code) {
case common_constants.EVENT_KEYNAMES.esc:
case common_constants.EVENT_KEYNAMES.escape:
this.close();
break;
case common_constants.EVENT_KEYNAMES.tab:
this.trapFocus(event);
break;
}
this.$emit("keydown", event);
},
"after-enter": (event) => {
this.$emit("update:show", true);
event.target === event.currentTarget && this.setFocusAfterTransition();
}
};
},
open() {
return `${!this.show}`;
},
hasFooterSlot() {
return !!this.$slots.footer;
},
bannerKindClass() {
return modal_constants.MODAL_BANNER_KINDS[this.bannerKind];
}
},
watch: {
show: {
handler(isShowing) {
var _a;
if (isShowing) {
this.previousActiveElement = document.activeElement;
common_utils.disableRootScrolling(common_utils.returnFirstEl(this.$el).getRootNode().host);
} else {
common_utils.enableRootScrolling(common_utils.returnFirstEl(this.$el).getRootNode().host);
(_a = this.previousActiveElement) == null ? void 0 : _a.focus();
this.previousActiveElement = null;
}
}
},
$props: {
immediate: true,
deep: true,
handler() {
this.validateProps();
}
}
},
methods: {
close() {
this.$emit("update:show", false);
},
setFocusAfterTransition() {
if (this.initialFocusElement === "first") {
this.focusFirstElement();
} else if (this.initialFocusElement.startsWith("#")) {
this.focusElementById(this.initialFocusElement);
} else if (this.initialFocusElement instanceof HTMLElement) {
this.initialFocusElement.focus();
}
},
trapFocus(e) {
if (this.show) {
this.focusTrappedTabPress(e);
}
},
validateProps() {
if (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`);
}
}
}
};
const _hoisted_1 = ["aria-describedby", "aria-labelledby"];
const _hoisted_2 = ["id"];
const _hoisted_3 = ["id"];
const _hoisted_4 = {
key: 4,
class: "d-modal__footer"
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_dt_icon_close = vue.resolveComponent("dt-icon-close");
const _component_dt_button = vue.resolveComponent("dt-button");
const _component_sr_only_close_button = vue.resolveComponent("sr-only-close-button");
const _component_dt_lazy_show = vue.resolveComponent("dt-lazy-show");
return vue.openBlock(), vue.createBlock(_component_dt_lazy_show, vue.mergeProps({
transition: "d-zoom",
show: $props.show,
class: [
"d-modal",
$data.MODAL_KIND_MODIFIERS[$props.kind],
$data.MODAL_SIZE_MODIFIERS[$props.size],
$props.modalClass
],
"data-qa": "dt-modal",
"aria-hidden": $options.open
}, vue.toHandlers($options.modalListeners)), {
default: vue.withCtx(() => [
$props.show && ($data.hasSlotContent(_ctx.$slots.banner) || $props.bannerTitle) ? (vue.openBlock(), vue.createElementBlock("div", {
key: 0,
"data-qa": "dt-modal-banner",
class: vue.normalizeClass([
"d-modal__banner",
$props.bannerClass,
$options.bannerKindClass
])
}, [
vue.renderSlot(_ctx.$slots, "banner", {}, () => [
vue.createTextVNode(vue.toDisplayString($props.bannerTitle), 1)
])
], 2)) : vue.createCommentVNode("", true),
vue.createVNode(vue.Transition, {
appear: "",
name: "d-modal__dialog"
}, {
default: vue.withCtx(() => [
vue.withDirectives(vue.createElementVNode("div", {
class: vue.normalizeClass([
"d-modal__dialog",
{ "d-modal__dialog--scrollable": $props.fixedHeaderFooter },
$props.dialogClass
]),
role: "dialog",
"aria-modal": "true",
"aria-describedby": $props.describedById,
"aria-labelledby": $props.labelledById
}, [
$data.hasSlotContent(_ctx.$slots.header) ? (vue.openBlock(), vue.createElementBlock("div", {
key: 0,
id: $props.labelledById,
class: "d-modal__header",
"data-qa": "dt-modal-title"
}, [
vue.renderSlot(_ctx.$slots, "header")
], 8, _hoisted_2)) : (vue.openBlock(), vue.createElementBlock("h2", {
key: 1,
id: $props.labelledById,
class: "d-modal__header",
"data-qa": "dt-modal-title"
}, vue.toDisplayString($props.title), 9, _hoisted_3)),
$data.hasSlotContent(_ctx.$slots.default) ? (vue.openBlock(), vue.createElementBlock("div", {
key: 2,
class: vue.normalizeClass([
"d-modal__content",
$props.contentClass
]),
"data-qa": "dt-modal-copy"
}, [
vue.renderSlot(_ctx.$slots, "default")
], 2)) : (vue.openBlock(), vue.createElementBlock("p", {
key: 3,
class: vue.normalizeClass([
"d-modal__content",
$props.contentClass
]),
"data-qa": "dt-modal-copy"
}, vue.toDisplayString($props.copy), 3)),
$options.hasFooterSlot ? (vue.openBlock(), vue.createElementBlock("footer", _hoisted_4, [
vue.renderSlot(_ctx.$slots, "footer")
])) : vue.createCommentVNode("", true),
!$props.hideClose ? (vue.openBlock(), vue.createBlock(_component_dt_button, vue.mergeProps({
key: 5,
class: "d-modal__close",
circle: "",
size: "lg",
importance: "clear",
"aria-label": $props.closeButtonProps.ariaLabel
}, $props.closeButtonProps, { onClick: $options.close }), {
icon: vue.withCtx(() => [
vue.createVNode(_component_dt_icon_close, { size: "400" })
]),
_: 1
}, 16, ["aria-label", "onClick"])) : vue.createCommentVNode("", true),
_ctx.showVisuallyHiddenClose ? (vue.openBlock(), vue.createBlock(_component_sr_only_close_button, {
key: 6,
"visually-hidden-close-label": _ctx.visuallyHiddenCloseLabel,
onClose: $options.close
}, null, 8, ["visually-hidden-close-label", "onClose"])) : vue.createCommentVNode("", true)
], 10, _hoisted_1), [
[vue.vShow, $props.show]
])
]),
_: 3
})
]),
_: 3
}, 16, ["show", "class", "aria-hidden"]);
}
const modal = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["render", _sfc_render]]);
exports.default = modal;
//# sourceMappingURL=modal.vue.cjs.map