123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`
- /**
- * Determine if the testing element is visible on screen no matter if its on the viewport or not
- */
- export const isVisible = (element: HTMLElement) => {
- if (process.env.NODE_ENV === 'test') return true
- const computed = getComputedStyle(element)
- // element.offsetParent won't work on fix positioned
- // WARNING: potential issue here, going to need some expert advices on this issue
- return computed.position === 'fixed' ? false : element.offsetParent !== null
- }
- export const obtainAllFocusableElements = (
- element: HTMLElement
- ): HTMLElement[] => {
- return Array.from(
- element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENT_SELECTORS)
- ).filter((item: HTMLElement) => isFocusable(item) && isVisible(item))
- }
- /**
- * @desc Determine if target element is focusable
- * @param element {HTMLElement}
- * @returns {Boolean} true if it is focusable
- */
- export const isFocusable = (element: HTMLElement): boolean => {
- if (
- element.tabIndex > 0 ||
- (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
- ) {
- return true
- }
- // HTMLButtonElement has disabled
- if ((element as HTMLButtonElement).disabled) {
- return false
- }
- switch (element.nodeName) {
- case 'A': {
- // casting current element to Specific HTMLElement in order to be more type precise
- return (
- !!(element as HTMLAnchorElement).href &&
- (element as HTMLAnchorElement).rel !== 'ignore'
- )
- }
- case 'INPUT': {
- return !(
- (element as HTMLInputElement).type === 'hidden' ||
- (element as HTMLInputElement).type === 'file'
- )
- }
- case 'BUTTON':
- case 'SELECT':
- case 'TEXTAREA': {
- return true
- }
- default: {
- return false
- }
- }
- }
- /**
- * @desc Set Attempt to set focus on the current node.
- * @param element
- * The node to attempt to focus on.
- * @returns
- * true if element is focused.
- */
- export const attemptFocus = (element: HTMLElement): boolean => {
- if (!isFocusable(element)) {
- return false
- }
- // Remove the old try catch block since there will be no error to be thrown
- element.focus?.()
- return document.activeElement === element
- }
- /**
- * Trigger an event
- * mouseenter, mouseleave, mouseover, keyup, change, click, etc.
- * @param {HTMLElement} elm
- * @param {String} name
- * @param {*} opts
- */
- export const triggerEvent = function (
- elm: HTMLElement,
- name: string,
- ...opts: Array<boolean>
- ): HTMLElement {
- let eventName: string
- if (name.includes('mouse') || name.includes('click')) {
- eventName = 'MouseEvents'
- } else if (name.includes('key')) {
- eventName = 'KeyboardEvent'
- } else {
- eventName = 'HTMLEvents'
- }
- const evt = document.createEvent(eventName)
- evt.initEvent(name, ...opts)
- elm.dispatchEvent(evt)
- return elm
- }
- export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns')
- export const getSibling = (
- el: HTMLElement,
- distance: number,
- elClass: string
- ) => {
- const { parentNode } = el
- if (!parentNode) return null
- const siblings = parentNode.querySelectorAll(elClass)
- const index = Array.prototype.indexOf.call(siblings, el)
- return siblings[index + distance] || null
- }
- export const focusNode = (el: HTMLElement) => {
- if (!el) return
- el.focus()
- !isLeaf(el) && el.click()
- }
|