UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

424 lines (423 loc) 14 kB
"use strict"; 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