@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
1 lines • 18.3 kB
Source Map (JSON)
{"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 && (hasSlotContent($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=\"hasSlotContent($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=\"hasSlotContent($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/vue3';\nimport Modal from '@/common/mixins/modal';\nimport {\n MODAL_BANNER_KINDS,\n MODAL_KIND_MODIFIERS,\n MODAL_SIZE_MODIFIERS,\n} from './modal_constants';\nimport { returnFirstEl, getUniqueString, hasSlotContent, 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 { DialtoneLocalization } from '@/localization';\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 compatConfig: { MODE: 3 },\n name: 'DtModal',\n\n components: {\n DtLazyShow,\n DtButton,\n DtIconClose,\n SrOnlyCloseButton,\n },\n\n mixins: [Modal],\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 * Native button click event\n *\n * @event click\n * @type {PointerEvent | KeyboardEvent}\n */\n 'click',\n\n /**\n * Native keydown event\n *\n * @event keydown\n * @type {KeyboardEvent}\n */\n 'keydown',\n\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 hasSlotContent,\n i18n: new DialtoneLocalization(),\n };\n },\n\n computed: {\n modalListeners () {\n return {\n click: event => {\n // Handle backdrop clicks for closing modal\n if (this.closeOnClick && event.target === event.currentTarget) {\n this.close();\n } else if (this.show && event.target !== event.currentTarget) {\n // Ensure focus stays within modal when clicking inside it\n this.handleModalClick(event);\n }\n\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': async () => {\n this.$emit('update:show', true);\n await this.setFocusAfterTransition();\n },\n\n focusin: event => {\n // Ensure focus stays within modal\n if (this.show && !this.$el.contains(event.target)) {\n event.preventDefault();\n this.focusFirstElement();\n }\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(returnFirstEl(this.$el).getRootNode().host);\n } else {\n enableRootScrolling(returnFirstEl(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 async setFocusAfterTransition () {\n if (this.initialFocusElement === 'first') {\n await this.focusFirstElement();\n } else if (this.initialFocusElement.startsWith('#')) {\n await 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 handleModalClick (event) {\n // Ensure focus stays within modal when clicking inside it\n const clickedElement = event.target;\n const focusableElements = this._getFocusableElements();\n\n // If the clicked element is not focusable, ensure focus stays in modal\n if (focusableElements.length && !focusableElements.includes(clickedElement)) {\n // Check if current active element is still within the modal\n if (!focusableElements.includes(document.activeElement)) {\n this.focusFirstElement();\n }\n }\n },\n },\n};\n</script>\n"],"names":["_sfc_main","DtLazyShow","DtButton","DtIconClose","SrOnlyCloseButton","Modal","getUniqueString","k","MODAL_KIND_MODIFIERS","s","MODAL_SIZE_MODIFIERS","kind","NOTICE_KINDS","initialFocusElement","MODAL_BANNER_KINDS","EVENT_KEYNAMES","hasSlotContent","DialtoneLocalization","event","isShowing","disableRootScrolling","returnFirstEl","enableRootScrolling","_a","e","clickedElement","focusableElements","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createBlock","_component_dt_lazy_show","_mergeProps","$props","$data","$options","_toHandlers","_withCtx","_ctx","_createElementBlock","_normalizeClass","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_createVNode","_Transition","_createElementVNode","_hoisted_4","_component_sr_only_close_button","_component_dt_button","_component_dt_icon_close"],"mappings":"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"}