115 lines
4.6 KiB
JavaScript
115 lines
4.6 KiB
JavaScript
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
||
|
|
||
|
function bind(el, binding) {
|
||
|
const { onFocusIn, onFocusOut } = binding.value || {};
|
||
|
|
||
|
el.$_pfocustrap_mutationobserver = new MutationObserver((mutationList) => {
|
||
|
mutationList.forEach((mutation) => {
|
||
|
if (mutation.type === 'childList' && !el.contains(document.activeElement)) {
|
||
|
const findNextFocusableElement = (el) => {
|
||
|
const focusableElement = DomHandler.isFocusableElement(el) ? el : DomHandler.getFirstFocusableElement(el);
|
||
|
|
||
|
return ObjectUtils.isNotEmpty(focusableElement) ? focusableElement : findNextFocusableElement(el.nextSibling);
|
||
|
};
|
||
|
|
||
|
DomHandler.focus(findNextFocusableElement(mutation.nextSibling));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
el.$_pfocustrap_mutationobserver.disconnect();
|
||
|
el.$_pfocustrap_mutationobserver.observe(el, {
|
||
|
childList: true
|
||
|
});
|
||
|
|
||
|
el.$_pfocustrap_focusinlistener = (event) => onFocusIn && onFocusIn(event);
|
||
|
el.$_pfocustrap_focusoutlistener = (event) => onFocusOut && onFocusOut(event);
|
||
|
|
||
|
el.addEventListener('focusin', el.$_pfocustrap_focusinlistener);
|
||
|
el.addEventListener('focusout', el.$_pfocustrap_focusoutlistener);
|
||
|
}
|
||
|
|
||
|
function unbind(el) {
|
||
|
el.$_pfocustrap_mutationobserver && el.$_pfocustrap_mutationobserver.disconnect();
|
||
|
el.$_pfocustrap_focusinlistener && el.removeEventListener('focusin', el.$_pfocustrap_focusinlistener) && (el.$_pfocustrap_focusinlistener = null);
|
||
|
el.$_pfocustrap_focusoutlistener && el.removeEventListener('focusout', el.$_pfocustrap_focusoutlistener) && (el.$_pfocustrap_focusoutlistener = null);
|
||
|
}
|
||
|
|
||
|
function autoFocus(el, binding) {
|
||
|
const { autoFocusSelector = '', firstFocusableSelector = '', autoFocus = false } = binding.value || {};
|
||
|
let focusableElement = DomHandler.getFirstFocusableElement(el, `[autofocus]:not(.p-hidden-focusable)${autoFocusSelector}`);
|
||
|
|
||
|
autoFocus && !focusableElement && (focusableElement = DomHandler.getFirstFocusableElement(el, `:not(.p-hidden-focusable)${firstFocusableSelector}`));
|
||
|
DomHandler.focus(focusableElement);
|
||
|
}
|
||
|
|
||
|
function onFirstHiddenElementFocus(event) {
|
||
|
const { currentTarget, relatedTarget } = event;
|
||
|
const focusableElement =
|
||
|
relatedTarget === currentTarget.$_pfocustrap_lasthiddenfocusableelement
|
||
|
? DomHandler.getFirstFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
|
||
|
: currentTarget.$_pfocustrap_lasthiddenfocusableelement;
|
||
|
|
||
|
DomHandler.focus(focusableElement);
|
||
|
}
|
||
|
|
||
|
function onLastHiddenElementFocus(event) {
|
||
|
const { currentTarget, relatedTarget } = event;
|
||
|
const focusableElement =
|
||
|
relatedTarget === currentTarget.$_pfocustrap_firsthiddenfocusableelement
|
||
|
? DomHandler.getLastFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
|
||
|
: currentTarget.$_pfocustrap_firsthiddenfocusableelement;
|
||
|
|
||
|
DomHandler.focus(focusableElement);
|
||
|
}
|
||
|
|
||
|
function createHiddenFocusableElements(el, binding) {
|
||
|
const { tabIndex = 0, firstFocusableSelector = '', lastFocusableSelector = '' } = binding.value || {};
|
||
|
|
||
|
const createFocusableElement = (onFocus) => {
|
||
|
const element = document.createElement('span');
|
||
|
|
||
|
element.classList = 'p-hidden-accessible p-hidden-focusable';
|
||
|
element.tabIndex = tabIndex;
|
||
|
element.setAttribute('aria-hidden', 'true');
|
||
|
element.setAttribute('role', 'presentation');
|
||
|
element.addEventListener('focus', onFocus);
|
||
|
|
||
|
return element;
|
||
|
};
|
||
|
|
||
|
const firstFocusableElement = createFocusableElement(onFirstHiddenElementFocus);
|
||
|
const lastFocusableElement = createFocusableElement(onLastHiddenElementFocus);
|
||
|
|
||
|
firstFocusableElement.$_pfocustrap_lasthiddenfocusableelement = lastFocusableElement;
|
||
|
firstFocusableElement.$_pfocustrap_focusableselector = firstFocusableSelector;
|
||
|
|
||
|
lastFocusableElement.$_pfocustrap_firsthiddenfocusableelement = firstFocusableElement;
|
||
|
lastFocusableElement.$_pfocustrap_focusableselector = lastFocusableSelector;
|
||
|
|
||
|
el.prepend(firstFocusableElement);
|
||
|
el.append(lastFocusableElement);
|
||
|
}
|
||
|
|
||
|
const FocusTrap = {
|
||
|
mounted(el, binding) {
|
||
|
const { disabled } = binding.value || {};
|
||
|
|
||
|
if (!disabled) {
|
||
|
createHiddenFocusableElements(el, binding);
|
||
|
bind(el, binding);
|
||
|
autoFocus(el, binding);
|
||
|
}
|
||
|
},
|
||
|
updated(el, binding) {
|
||
|
const { disabled } = binding.value || {};
|
||
|
|
||
|
disabled && unbind(el);
|
||
|
},
|
||
|
unmounted(el) {
|
||
|
unbind(el);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
export default FocusTrap;
|