UNPKG

@taiga-ui/cdk

Version:

Base library for creating Angular components and applications using Taiga UI principles regarding of actual visual appearance

129 lines (119 loc) 4.26 kB
import { toSignal } from '@angular/core/rxjs-interop'; import { TUI_TRUE_HANDLER, TUI_FALSE_HANDLER, svgNodeFilter } from '@taiga-ui/cdk/constants'; import { tuiUntrackedScheduler } from '@taiga-ui/cdk/observables'; import { merge, fromEvent, map, observeOn } from 'rxjs'; import { tuiIsHTMLElement } from '@taiga-ui/cdk/utils/dom'; function tuiFocusedIn(node) { return toSignal(merge(fromEvent(node, 'focus', { capture: true }).pipe(map(TUI_TRUE_HANDLER)), fromEvent(node, 'blur', { capture: true }).pipe(map(TUI_FALSE_HANDLER))).pipe(observeOn(tuiUntrackedScheduler)), { initialValue: false }); } /** * Checks for signs that element can be focused with keyboard. tabIndex above 0 is ignored to * only target natural focus order. Not checking the possibility of an element to * be focused, for example element can have display: none applied to it or any other * circumstances could prevent actual focus. */ function tuiIsFocusable(element) { if (element.matches(':disabled') || element.getAttribute('tabIndex') === '-1') { return false; } if ((tuiIsHTMLElement(element) && element.isContentEditable) || element.getAttribute('tabIndex') === '0') { return true; } switch (element.tagName) { case 'A': case 'LINK': return element.hasAttribute('href'); case 'AUDIO': case 'VIDEO': return element.hasAttribute('controls'); case 'BUTTON': case 'SELECT': case 'TEXTAREA': return true; case 'INPUT': return element.getAttribute('type') !== 'hidden'; default: return false; } } /** * @description: * Finds the closest element that can be focused with a keyboard or mouse in theory */ function tuiGetClosestFocusable({ initial, root, previous = false, }) { if (!root.ownerDocument) { return null; } const treeWalker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, svgNodeFilter); treeWalker.currentNode = initial; do { if (tuiIsHTMLElement(treeWalker.currentNode)) { initial = treeWalker.currentNode; } if (tuiIsHTMLElement(initial) && tuiIsFocusable(initial)) { return initial; } } while (previous ? treeWalker.previousNode() : treeWalker.nextNode()); return null; } /** * Returns current active element, including shadow dom * * @return element or null */ function tuiGetFocused({ activeElement }) { if (!activeElement?.shadowRoot) { return activeElement; } let element = activeElement.shadowRoot.activeElement; while (element?.shadowRoot) { element = element.shadowRoot.activeElement; } return element; } /** * Checks if element is focused. * * Could return true even after blur since element remains focused if you switch away from a browser tab. * * @param node or null (as a common return value of DOM nodes walking) * @return true if focused */ function tuiIsFocused(node) { return (!!node?.ownerDocument && tuiGetFocused(node.ownerDocument) === node && node.ownerDocument.hasFocus()); } /** * Checks if focused element is within given element. * * @param node * @return true if focused node is contained within element */ function tuiIsFocusedIn(node) { const focused = node?.ownerDocument && tuiGetFocused(node.ownerDocument); return !!focused && node.contains(focused) && node.ownerDocument?.hasFocus(); } /** * Utility method for moving focus in a list of elements * * @param currentIndex currently focused index * @param elements array of focusable elements * @param step a step to move focus by, typically -1 or 1 */ function tuiMoveFocus(currentIndex, elements, step) { currentIndex += step; while (currentIndex >= 0 && currentIndex < elements.length) { elements[currentIndex]?.focus(); if (tuiIsFocused(elements[currentIndex])) { return; } currentIndex += step; } } /** * Generated bundle index. Do not edit. */ export { tuiFocusedIn, tuiGetClosestFocusable, tuiGetFocused, tuiIsFocusable, tuiIsFocused, tuiIsFocusedIn, tuiMoveFocus }; //# sourceMappingURL=taiga-ui-cdk-utils-focus.mjs.map