{"version":3,"names":["getFocusableSelector","getFocusableElements","container","Array","from","querySelectorAll","getFirstFocusableElement","focusable","length","getLastFocusableElement","focusElementInShadowRoot","shadowRoot","firstFocusableInShadowRoot","isNullOrUndefined","focus","focusElement","element","observeContainer","onIntersectionCallback","window","intersectionObserver","IntersectionObserver","entries","map","entry","isIntersecting","target","unobserve","observe","setTimeout","focusArea","containerElement","onFocusOutCallback","startFromEnd","parseInt","getAttribute","unshift","lastFocusable","firstFocusable","onEdgeElementBlur","evt","nextElement","relatedTarget","getIsElementDescendantOfElement","removeEventListener","addEventListener","setFocusOnArea","areaContainer","observedElement"],"sources":["src/utils/focusHelper.ts"],"sourcesContent":["import { getIsElementDescendantOfElement } from './DOMHelper';\nimport { isNullOrUndefined } from './collection';\n\n/**\n * @returns {string} The selector to be used to find all focusables\n */\nexport const getFocusableSelector = (): string => {\n  return 'ads-search:not([data-disabled]), ads-button:not([data-disabled]), ads-cta-button:not([data-disabled]), ads-dropdown:not([data-disabled]), ads-vertical-tabs:not([data-disabled]), [href],button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n};\n\n/**\n * Returns all the focusable elements under the given container\n * @param {Element | ShadowRoot} container The container element\n * @returns {Element[]} Focusable elements\n */\nexport const getFocusableElements = (container: Element | ShadowRoot): Element[] => {\n  // get all tabbable elements in the container\n  return Array.from(container.querySelectorAll(getFocusableSelector()));\n};\n\n/**\n * Returns the first focusable element in the container element\n * @param {Element | ShadowRoot} container The container element\n * @returns {Element} First focusable element\n */\nexport const getFirstFocusableElement = (container: Element | ShadowRoot): Element => {\n  const focusable = getFocusableElements(container);\n  return focusable.length > 0 ? focusable[0] : null;\n};\n\n/**\n * Returns the last focusable element in the container element\n * @param {Element | ShadowRoot} container The container element\n * @returns {Element} Last focusable element\n */\nexport const getLastFocusableElement = (container: Element | ShadowRoot): Element => {\n  const focusable = getFocusableElements(container);\n  return focusable.length > 0 ? focusable[focusable.length - 1] : null;\n};\n\n/**\n * Sets focus to the first focusable element in a shadowroot\n * @param {ShadowRoot} shadowRoot Shadowroot\n */\nconst focusElementInShadowRoot = (shadowRoot: ShadowRoot): void => {\n  const firstFocusableInShadowRoot = getFirstFocusableElement(shadowRoot) as HTMLElement;\n  !isNullOrUndefined(firstFocusableInShadowRoot) ? firstFocusableInShadowRoot.focus() : null;\n};\n\n/**\n * Sets focus on the given element. If the element has a shadowroot it will set focus to the first focusable element in the shadowroot\n * @param {HTMLElement} element Element\n */\nconst focusElement = (element: HTMLElement): void => {\n  if (!isNullOrUndefined(element.shadowRoot)) {\n    focusElementInShadowRoot(element.shadowRoot);\n  } else {\n    element.focus();\n  }\n};\n\n/**\n * Observer that observes the given element and fires a callback when it becoes visible\n * If IntersectionObserver is not supported by the browser it will use a timeout\n * @param {Element} element to observe\n * @param {Function} onIntersectionCallback Callback when element becomes visible\n */\nexport const observeContainer = (element: Element, onIntersectionCallback: (observedElement: Element) => void): void => {\n  if ('IntersectionObserver' in window) {\n    const intersectionObserver = new IntersectionObserver((entries) => {\n      entries.map((entry) => {\n        if (entry.isIntersecting) {\n          onIntersectionCallback(entry.target);\n          intersectionObserver.unobserve(element);\n        }\n      });\n    });\n    intersectionObserver.observe(element);\n  } else {\n    setTimeout(() => {\n      onIntersectionCallback(element);\n    }, 50);\n  }\n};\n\n/**\n * Given a container element, it will set focus on the first tabbable element and fire callback when tabbing out of the container. Respects shift+tab\n * @param {Element} containerElement The container element for focus\n * @param {Function} onFocusOutCallback Callback when \"focusout\" triggers when leaving the container\n * @param {boolean} startFromEnd Optional: Passing true will focus the last element\n */\nconst focusArea = (containerElement: Element, onFocusOutCallback?: (event: FocusEvent) => void, startFromEnd = false): void => {\n  // get all tabbable elements in the container\n  const focusable = getFocusableElements(containerElement) as HTMLElement[];\n\n  // add the container to the array if it has tabIndex\n  if (parseInt(containerElement.getAttribute('tabIndex'), 10) >= 0) {\n    focusable.unshift(containerElement as HTMLElement);\n  }\n  // get last tabbable element in container\n  const lastFocusable = focusable[focusable.length - 1];\n  // get first tabbable element in container\n  const firstFocusable = focusable[0];\n\n  if (focusable.length > 0) {\n    if (startFromEnd) {\n      focusElement(lastFocusable);\n    } else {\n      focusElement(firstFocusable);\n    }\n\n    // we need a function reference to be used for the even listener so we can remove it when it's over.\n    const onEdgeElementBlur = (evt: FocusEvent): void => {\n      const nextElement = evt.relatedTarget as Element;\n      //check if the next element is within the container still\n      if (isNullOrUndefined(nextElement) || getIsElementDescendantOfElement(nextElement, containerElement)) {\n        return;\n      }\n      lastFocusable.removeEventListener('blur', onEdgeElementBlur);\n      firstFocusable.removeEventListener('blur', onEdgeElementBlur);\n      if (!isNullOrUndefined(onFocusOutCallback)) {\n        onFocusOutCallback(evt);\n      }\n    };\n\n    lastFocusable.addEventListener('blur', onEdgeElementBlur);\n    firstFocusable.addEventListener('blur', onEdgeElementBlur);\n  }\n};\n\n/**\n * Sets focus on the first tabbable element in a container when it becomes visible. Executes the callback function when the user is tabbing out of the container. Respects shift+tab\n * @param {Element} areaContainer Container to focus on\n * @param {Function} onFocusOutCallback Fires when \"focusout\" triggers on the last tabbable element\n * @param {boolean} startFromEnd Optional: Passing true will focus the last element\n */\nexport const setFocusOnArea = (areaContainer: Element, onFocusOutCallback?: (event: FocusEvent) => void, startFromEnd = false): void => {\n  observeContainer(areaContainer, (observedElement: Element) => {\n    focusArea(observedElement, onFocusOutCallback, startFromEnd);\n  });\n};\n"],"mappings":"8EAMaA,EAAuB,IAC3B,4T,MAQIC,EAAwBC,GAE5BC,MAAMC,KAAKF,EAAUG,iBAAiBL,M,MAQlCM,EAA4BJ,IACvC,MAAMK,EAAYN,EAAqBC,GACvC,OAAOK,EAAUC,OAAS,EAAID,EAAU,GAAK,IAAI,E,MAQtCE,EAA2BP,IACtC,MAAMK,EAAYN,EAAqBC,GACvC,OAAOK,EAAUC,OAAS,EAAID,EAAUA,EAAUC,OAAS,GAAK,IAAI,EAOtE,MAAME,EAA4BC,IAChC,MAAMC,EAA6BN,EAAyBK,IAC3DE,EAAkBD,GAA8BA,EAA2BE,QAAU,IAAI,EAO5F,MAAMC,EAAgBC,IACpB,IAAKH,EAAkBG,EAAQL,YAAa,CAC1CD,EAAyBM,EAAQL,W,KAC5B,CACLK,EAAQF,O,SAUCG,EAAmB,CAACD,EAAkBE,KACjD,GAAI,yBAA0BC,OAAQ,CACpC,MAAMC,EAAuB,IAAIC,sBAAsBC,IACrDA,EAAQC,KAAKC,IACX,GAAIA,EAAMC,eAAgB,CACxBP,EAAuBM,EAAME,QAC7BN,EAAqBO,UAAUX,E,IAEjC,IAEJI,EAAqBQ,QAAQZ,E,KACxB,CACLa,YAAW,KACTX,EAAuBF,EAAQ,GAC9B,G,GAUP,MAAMc,EAAY,CAACC,EAA2BC,EAAkDC,EAAe,SAE7G,MAAM1B,EAAYN,EAAqB8B,GAGvC,GAAIG,SAASH,EAAiBI,aAAa,YAAa,KAAO,EAAG,CAChE5B,EAAU6B,QAAQL,E,CAGpB,MAAMM,EAAgB9B,EAAUA,EAAUC,OAAS,GAEnD,MAAM8B,EAAiB/B,EAAU,GAEjC,GAAIA,EAAUC,OAAS,EAAG,CACxB,GAAIyB,EAAc,CAChBlB,EAAasB,E,KACR,CACLtB,EAAauB,E,CAIf,MAAMC,EAAqBC,IACzB,MAAMC,EAAcD,EAAIE,cAExB,GAAI7B,EAAkB4B,IAAgBE,EAAgCF,EAAaV,GAAmB,CACpG,M,CAEFM,EAAcO,oBAAoB,OAAQL,GAC1CD,EAAeM,oBAAoB,OAAQL,GAC3C,IAAK1B,EAAkBmB,GAAqB,CAC1CA,EAAmBQ,E,GAIvBH,EAAcQ,iBAAiB,OAAQN,GACvCD,EAAeO,iBAAiB,OAAQN,E,SAU/BO,EAAiB,CAACC,EAAwBf,EAAkDC,EAAe,SACtHhB,EAAiB8B,GAAgBC,IAC/BlB,EAAUkB,EAAiBhB,EAAoBC,EAAa,GAC5D,E"}