UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

1 lines 14.1 kB
{"version":3,"file":"modal.cjs","sources":["../../../components/modal/modal.vue"],"sourcesContent":["<template>\n <dt-lazy-show\n transition=\"d-zoom\"\n :show=\"show\"\n :class=\"[\n 'd-modal',\n MODAL_KIND_MODIFIERS[kind],\n MODAL_SIZE_MODIFIERS[size],\n modalClass,\n ]\"\n data-qa=\"dt-modal\"\n :aria-hidden=\"open\"\n v-on=\"modalListeners\"\n >\n <div\n v-if=\"show && ($slots.banner || bannerTitle)\"\n data-qa=\"dt-modal-banner\"\n :class=\"[\n 'd-modal__banner',\n bannerClass,\n bannerKindClass,\n ]\"\n >\n <!-- @slot Slot for the banner, defaults to bannerTitle prop -->\n <slot name=\"banner\">\n {{ bannerTitle }}\n </slot>\n </div>\n <transition\n appear\n name=\"d-modal__dialog\"\n >\n <div\n v-show=\"show\"\n :class=\"[\n 'd-modal__dialog',\n { 'd-modal__dialog--scrollable': fixedHeaderFooter },\n dialogClass,\n ]\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-describedby=\"describedById\"\n :aria-labelledby=\"labelledById\"\n >\n <div\n v-if=\"$slots.header\"\n :id=\"labelledById\"\n class=\"d-modal__header\"\n data-qa=\"dt-modal-title\"\n >\n <!-- @slot Slot for dialog header section, taking the place of any \"title\" text prop -->\n <slot name=\"header\" />\n </div>\n <h2\n v-else\n :id=\"labelledById\"\n class=\"d-modal__header\"\n data-qa=\"dt-modal-title\"\n >\n {{ title }}\n </h2>\n <div\n v-if=\"$slots.default\"\n :class=\"[\n 'd-modal__content',\n contentClass,\n ]\"\n data-qa=\"dt-modal-copy\"\n >\n <!-- @slot Default slot for dialog body section, taking the place of any \"copy\" text prop -->\n <slot />\n </div>\n <p\n v-else\n :class=\"[\n 'd-modal__content',\n contentClass,\n ]\"\n data-qa=\"dt-modal-copy\"\n >\n {{ copy }}\n </p>\n <footer\n v-if=\"hasFooterSlot\"\n class=\"d-modal__footer\"\n >\n <!-- @slot Slot for dialog footer content, often containing cancel and confirm buttons. -->\n <slot name=\"footer\" />\n </footer>\n <sr-only-close-button\n v-if=\"hideClose\"\n @close=\"close\"\n />\n <dt-button\n v-else\n class=\"d-modal__close\"\n data-qa=\"dt-modal-close-button\"\n circle\n size=\"lg\"\n importance=\"clear\"\n :aria-label=\"closeButtonTitle\"\n :title=\"closeButtonTitle\"\n @click=\"close\"\n >\n <template #icon>\n <dt-icon-close\n size=\"400\"\n />\n </template>\n </dt-button>\n </div>\n </transition>\n </dt-lazy-show>\n</template>\n\n<script>\n/* eslint-disable max-lines */\nimport { DtButton } from '@/components/button';\nimport { DtIconClose } from '@dialpad/dialtone-icons/vue2';\nimport Modal from '@/common/mixins/modal';\nimport {\n MODAL_BANNER_KINDS,\n MODAL_KIND_MODIFIERS,\n MODAL_SIZE_MODIFIERS,\n} from './modal_constants';\nimport { getUniqueString, disableRootScrolling, enableRootScrolling } from '@/common/utils';\nimport { DtLazyShow } from '@/components/lazy_show';\nimport { EVENT_KEYNAMES } from '@/common/constants';\nimport SrOnlyCloseButton from '@/common/sr_only_close_button.vue';\nimport { NOTICE_KINDS } from '@/components/notice';\nimport { DtLocalizationMixin } from '@/common/mixins';\n\n/**\n * Modals focus the user’s attention exclusively on one task or piece of information\n * via a window that sits on top of the page content.\n * @see https://dialtone.dialpad.com/components/modal.html\n */\nexport default {\n name: 'DtModal',\n\n components: {\n DtLazyShow,\n DtButton,\n DtIconClose,\n SrOnlyCloseButton,\n },\n\n mixins: [Modal, DtLocalizationMixin],\n\n props: {\n /**\n * Body text to display as the modal's main content.\n */\n copy: {\n type: String,\n default: '',\n },\n\n /**\n * Id to use for the dialog's aria-describedby.\n * Recommended only if the dialog content itself isn't enough to give full context,\n * as screen readers should recite the dialog contents by default before any aria-description.\n */\n describedById: {\n type: String,\n default: '',\n },\n\n /**\n * Id to use for the dialog's aria-labelledby.\n */\n labelledById: {\n type: String,\n default: function () { return getUniqueString(); },\n },\n\n /**\n * Whether the modal should be shown.\n * Parent component can sync on this value to control the modal's visibility.\n * @values true, false\n */\n show: {\n type: Boolean,\n default: false,\n },\n\n /**\n * Title text to display in the modal header.\n */\n title: {\n type: String,\n default: '',\n },\n\n /**\n * Title text to display in the modal banner.\n */\n bannerTitle: {\n type: String,\n default: '',\n },\n\n /**\n * The theme of the modal. kind - default or danger,\n * @values default, danger\n */\n kind: {\n type: String,\n default: 'default',\n validator: (k) => Object.keys(MODAL_KIND_MODIFIERS).includes(k),\n },\n\n /**\n * The size of the modal. size - default or full,\n * @values default, full\n */\n size: {\n type: String,\n default: 'default',\n validator: (s) => Object.keys(MODAL_SIZE_MODIFIERS).includes(s),\n },\n\n /**\n * Additional class name for the root modal element.\n * Can accept String, Object, and Array, i.e. has the\n * same API as Vue's built-in handling of the class attribute.\n */\n modalClass: {\n type: [String, Object, Array],\n default: '',\n },\n\n /**\n * Additional class name for the dialog element within the modal.\n * Can accept String, Object, and Array, i.e. has the\n * same API as Vue's built-in handling of the class attribute.\n */\n dialogClass: {\n type: [String, Object, Array],\n default: '',\n },\n\n /**\n * Additional class name for the content element within the modal.\n * Can accept String, Object, and Array, i.e. has the\n * same API as Vue's built-in handling of the class attribute.\n */\n contentClass: {\n type: [String, Object, Array],\n default: '',\n },\n\n /**\n * Sets the color of the banner.\n * @values base, error, info, success, warning\n */\n bannerKind: {\n type: String,\n default: 'warning',\n validate (kind) {\n return NOTICE_KINDS.includes(kind);\n },\n },\n\n /**\n * Additional class name for the banner element within the modal.\n * Can accept String, Object, and Array, i.e. has the\n * same API as Vue's built-in handling of the class attribute.\n */\n bannerClass: {\n type: [String, Object, Array],\n default: '',\n },\n\n /**\n * Hides the close button on the modal\n * @values true, false\n */\n hideClose: {\n type: Boolean,\n default: false,\n },\n\n /**\n * Whether the modal will close when you click outside of the dialog on the overlay.\n * @values true, false\n */\n closeOnClick: {\n type: Boolean,\n default: true,\n },\n\n /**\n * Scrollable modal that allows scroll the modal content keeping the header and footer fixed\n * @values true, false\n */\n fixedHeaderFooter: {\n type: Boolean,\n default: true,\n },\n\n /**\n * The element that is focused when the modal is opened. This can be an\n * HTMLElement within the modal, a string starting with '#' which will\n * find the element by ID. 'first' which will automatically focus\n * the first element, or 'dialog' which will focus the dialog window itself.\n * If the dialog is modal this prop cannot be 'none'.\n */\n initialFocusElement: {\n type: [String, HTMLElement],\n default: 'first',\n validator: initialFocusElement => {\n return initialFocusElement === 'first' ||\n (initialFocusElement instanceof HTMLElement) ||\n initialFocusElement.startsWith('#');\n },\n },\n },\n\n emits: [\n /**\n * The modal will emit a \"false\" boolean value for this event when the user performs a modal-closing action.\n * Parent components can sync on this value to create a 2-way binding to control modal visibility.\n *\n * @event update:show\n * @type {Boolean}\n */\n 'update:show',\n ],\n\n data () {\n return {\n MODAL_KIND_MODIFIERS,\n MODAL_SIZE_MODIFIERS,\n MODAL_BANNER_KINDS,\n EVENT_KEYNAMES,\n };\n },\n\n computed: {\n modalListeners () {\n return {\n ...this.$listeners,\n\n click: event => {\n if (!this.closeOnClick) return;\n (event.target === event.currentTarget) && this.close();\n this.$emit('click', event);\n },\n\n keydown: event => {\n switch (event.code) {\n case EVENT_KEYNAMES.esc:\n case EVENT_KEYNAMES.escape:\n this.close();\n break;\n case EVENT_KEYNAMES.tab:\n this.trapFocus(event);\n break;\n }\n this.$emit('keydown', event);\n },\n\n 'after-enter': event => {\n this.$emit('update:show', true);\n (event.target === event.currentTarget) && this.setFocusAfterTransition();\n },\n };\n },\n\n open () {\n return `${!this.show}`;\n },\n\n hasFooterSlot () {\n return !!this.$slots.footer;\n },\n\n bannerKindClass () {\n return MODAL_BANNER_KINDS[this.bannerKind];\n },\n\n closeButtonTitle () {\n return this.i18n.$t('DIALTONE_CLOSE_BUTTON');\n },\n },\n\n watch: {\n show: {\n handler (isShowing) {\n if (isShowing) {\n // Set a reference to the previously-active element, to which we'll return focus on modal close.\n this.previousActiveElement = document.activeElement;\n disableRootScrolling(this.$el.getRootNode().host);\n } else {\n enableRootScrolling(this.$el.getRootNode().host);\n // Modal is being hidden, so return focus to the previously active element before clearing the reference.\n this.previousActiveElement?.focus();\n this.previousActiveElement = null;\n }\n },\n },\n },\n\n methods: {\n close () {\n this.$emit('update:show', false);\n },\n\n setFocusAfterTransition () {\n if (this.initialFocusElement === 'first') {\n this.focusFirstElement();\n } else if (this.initialFocusElement.startsWith('#')) {\n this.focusElementById(this.initialFocusElement);\n } else if (this.initialFocusElement instanceof HTMLElement) {\n this.initialFocusElement.focus();\n }\n },\n\n trapFocus (e) {\n if (this.show) {\n this.focusTrappedTabPress(e);\n }\n },\n },\n};\n</script>\n"],"names":["_sfc_main","DtLazyShow","DtButton","DtIconClose","SrOnlyCloseButton","Modal","DtLocalizationMixin","getUniqueString","k","MODAL_KIND_MODIFIERS","s","MODAL_SIZE_MODIFIERS","kind","NOTICE_KINDS","initialFocusElement","MODAL_BANNER_KINDS","EVENT_KEYNAMES","event","isShowing","disableRootScrolling","enableRootScrolling","_a"],"mappings":"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"}