@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
1 lines • 6.47 kB
Source Map (JSON)
{"version":3,"file":"modal.cjs","names":["returnFirstEl"],"sources":["../../../common/mixins/modal.js"],"sourcesContent":["import { returnFirstEl } from '@/common/utils';\n\nconst focusableAttrs = ':not(:disabled):not([aria-disabled=\"true\"]):not([role=\"presentation\"])';\nconst tabbableAttrs = `${focusableAttrs}:not([tabindex=\"-1\"])`;\nconst focusableElementsList = `button,[href],input,select,textarea,details,[tabindex]`;\n\n/**\n * This mixin provides the methods to automatically trap tab focus within\n * the component this mixin is on, meaning it is not possible to tab out\n * of the component without dismissing it.\n *\n * Useful for accessibility reasons on things like important actionable alerts.\n *\n * Use focusFirstElement to focus on the first tabbable element within your component, and call\n * focusTrappedTabPress every time tab is pressed to trap tab within this\n * component.\n *\n * Note that focusFirstElement WILL focus elements with tabindex=\"-1\",\n * however focusTrappedTabPress will not.\n * @displayName Modal Mixin\n */\nexport default {\n methods: {\n /**\n * get the first focusable element in your component, includes tabindex=\"-1\".\n * @param {object} el - optional - ref of dom element to trap focus on.\n * will default to the root node of the vue component\n */\n async getFirstFocusableElement (el) {\n await this.$nextTick();\n const focusableElements = this._getFocusableElements(el, true);\n return this._getFirstFocusElement(focusableElements);\n },\n\n /**\n * set focus to the first focusable element in your component, includes tabindex=\"-1\".\n * @param {object} el - optional - ref of dom element to trap focus on.\n * will default to the root node of the vue component\n */\n async focusFirstElement (el = this.$el) {\n const elToFocus = await this.getFirstFocusableElement(returnFirstEl(el));\n elToFocus?.focus({ preventScroll: true });\n },\n\n async focusElementById (elementId) {\n await this.$nextTick();\n const result = returnFirstEl(this.$el)?.querySelector(elementId);\n if (result) {\n result.focus();\n return;\n }\n\n // eslint-disable-next-line no-console\n console.warn('Could not find the element specified in dt-modal prop \"initialFocusElement\". ' +\n 'Defaulting to focusing the first element.');\n await this.focusFirstElement();\n },\n\n /**\n * internal use only.\n *\n * @param focusableElements - list of focusable elements\n * @returns {*} - first DOM element that is focusable.\n * @private\n */\n _getFirstFocusElement (focusableElements) {\n if (!focusableElements.length) {\n return;\n }\n let firstFocusEl = focusableElements[0];\n // If first element is a checkbox, put focus on the selected checkbox or the first checkbox if none are selected.\n if (firstFocusEl.matches('[type=\"radio\"]:not(:checked)')) {\n firstFocusEl = focusableElements.find(el => el.checked && el.name === firstFocusEl.name) || firstFocusEl;\n }\n return firstFocusEl;\n },\n\n /**\n * internal use only.\n *\n * gets all the focusable elements within the component\n * and sets the first and last of those elements.\n *\n * @param {object} el - the root dom element to find focusable elements in.\n * @param {bool} includeNegativeTabIndex - will include tabindex=\"-1\" in the list of focusable elements.\n */\n _getFocusableElements (el = this.$el, includeNegativeTabIndex = false) {\n el = returnFirstEl(el);\n if (!el) return [];\n const focusableContent = [...el.querySelectorAll(focusableElementsList)];\n return focusableContent.filter((fc) => {\n const style = window.getComputedStyle(fc);\n return style.getPropertyValue('display') !== 'none' &&\n style.getPropertyValue('visibility') !== 'hidden' &&\n fc.matches(includeNegativeTabIndex ? focusableAttrs : tabbableAttrs);\n });\n },\n\n /**\n * tabs to the next element contained within your component, does not include tabindex=\"-1\".\n * @param {object} e - keypress event\n * @param {object} el - optional - ref of dom element to trap focus on.\n * will default to the root node of the vue component\n */\n focusTrappedTabPress (e, el) {\n const isTabPressed = e.key === 'Tab';\n\n if (!isTabPressed) {\n return;\n }\n\n const focusableElements = this._getFocusableElements(el);\n if (!focusableElements.length) {\n e.preventDefault();\n return;\n }\n\n const firstFocusableElement = this._getFirstFocusElement(focusableElements);\n const lastFocusableElement = focusableElements[focusableElements.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === firstFocusableElement) {\n lastFocusableElement.focus();\n e.preventDefault();\n }\n } else {\n if (document.activeElement === lastFocusableElement) {\n firstFocusableElement.focus();\n e.preventDefault();\n }\n }\n },\n },\n};\n"],"mappings":"qIAEA,IAAM,EAAiB,yEACjB,EAAgB,GAAG,EAAe,uBAClC,EAAwB,yDAiB9B,EAAe,CACb,QAAS,CAMP,MAAM,yBAA0B,EAAI,CAClC,MAAM,KAAK,WAAW,CACtB,IAAM,EAAoB,KAAK,sBAAsB,EAAI,GAAK,CAC9D,OAAO,KAAK,sBAAsB,EAAkB,EAQtD,MAAM,kBAAmB,EAAK,KAAK,IAAK,EACpB,MAAM,KAAK,yBAAyBA,EAAAA,cAAc,EAAG,CAAC,GAC7D,MAAM,CAAE,cAAe,GAAM,CAAC,EAG3C,MAAM,iBAAkB,EAAW,CACjC,MAAM,KAAK,WAAW,CACtB,IAAM,EAASA,EAAAA,cAAc,KAAK,IAAI,EAAE,cAAc,EAAU,CAChE,GAAI,EAAQ,CACV,EAAO,OAAO,CACd,OAIF,QAAQ,KAAK,yHACiC,CAC9C,MAAM,KAAK,mBAAmB,EAUhC,sBAAuB,EAAmB,CACxC,GAAI,CAAC,EAAkB,OACrB,OAEF,IAAI,EAAe,EAAkB,GAKrC,OAHI,EAAa,QAAQ,+BAA+B,GACtD,EAAe,EAAkB,KAAK,GAAM,EAAG,SAAW,EAAG,OAAS,EAAa,KAAK,EAAI,GAEvF,GAYT,sBAAuB,EAAK,KAAK,IAAK,EAA0B,GAAO,CAIrE,MAHA,GAAKA,EAAAA,cAAc,EAAG,CACjB,EACoB,CAAC,GAAG,EAAG,iBAAiB,EAAsB,CAAC,CAChD,OAAQ,GAAO,CACrC,IAAM,EAAQ,OAAO,iBAAiB,EAAG,CACzC,OAAO,EAAM,iBAAiB,UAAU,GAAK,QAC3C,EAAM,iBAAiB,aAAa,GAAK,UACzC,EAAG,QAAQ,EAA0B,EAAiB,EAAc,EACtE,CAPc,EAAE,EAgBpB,qBAAsB,EAAG,EAAI,CAG3B,GAFqB,EAAE,MAAQ,MAG7B,OAGF,IAAM,EAAoB,KAAK,sBAAsB,EAAG,CACxD,GAAI,CAAC,EAAkB,OAAQ,CAC7B,EAAE,gBAAgB,CAClB,OAGF,IAAM,EAAwB,KAAK,sBAAsB,EAAkB,CACrE,EAAuB,EAAkB,EAAkB,OAAS,GAEtE,EAAE,SACA,SAAS,gBAAkB,IAC7B,EAAqB,OAAO,CAC5B,EAAE,gBAAgB,EAGhB,SAAS,gBAAkB,IAC7B,EAAsB,OAAO,CAC7B,EAAE,gBAAgB,GAIzB,CACF"}