@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
JavaScript
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