{"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 { 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  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     * 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      i18n: new DialtoneLocalization(),\n    };\n  },\n\n  computed: {\n    modalListeners () {\n      return {\n        ...this.$listeners,\n\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    },\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    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","DialtoneLocalization","event","isShowing","disableRootScrolling","enableRootScrolling","_a","clickedElement","focusableElements"],"mappings":"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"}