2022-12-08 11:04:25 +00:00
|
|
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
2023-07-03 22:20:35 +00:00
|
|
|
import BaseFocusTrap from './BaseFocusTrap';
|
2022-12-08 11:04:25 +00:00
|
|
|
|
2023-07-03 22:20:35 +00:00
|
|
|
const FocusTrap = BaseFocusTrap.extend('focustrap', {
|
2022-12-08 11:04:25 +00:00
|
|
|
mounted(el, binding) {
|
|
|
|
const { disabled } = binding.value || {};
|
|
|
|
|
|
|
|
if (!disabled) {
|
2023-07-03 22:20:35 +00:00
|
|
|
this.createHiddenFocusableElements(el, binding);
|
|
|
|
this.bind(el, binding);
|
2024-01-21 11:56:17 +00:00
|
|
|
this.autoElementFocus(el, binding);
|
2022-12-08 11:04:25 +00:00
|
|
|
}
|
2023-06-22 13:53:04 +00:00
|
|
|
|
2023-07-07 12:00:11 +00:00
|
|
|
el.setAttribute('data-pd-focustrap', true);
|
2023-06-22 13:53:04 +00:00
|
|
|
|
2023-07-03 22:20:35 +00:00
|
|
|
this.$el = el;
|
2022-12-08 11:04:25 +00:00
|
|
|
},
|
|
|
|
updated(el, binding) {
|
|
|
|
const { disabled } = binding.value || {};
|
|
|
|
|
2023-07-03 22:20:35 +00:00
|
|
|
disabled && this.unbind(el);
|
2022-12-08 11:04:25 +00:00
|
|
|
},
|
|
|
|
unmounted(el) {
|
2023-07-03 22:20:35 +00:00
|
|
|
this.unbind(el);
|
|
|
|
},
|
|
|
|
methods: {
|
2023-07-03 23:12:09 +00:00
|
|
|
getComputedSelector(selector) {
|
2023-07-06 14:57:08 +00:00
|
|
|
return `:not(.p-hidden-focusable):not([data-p-hidden-focusable="true"])${selector ?? ''}`;
|
2023-07-03 23:12:09 +00:00
|
|
|
},
|
2023-07-03 22:20:35 +00:00
|
|
|
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)) {
|
2023-07-06 14:57:08 +00:00
|
|
|
const findNextFocusableElement = (_el) => {
|
|
|
|
const focusableElement = DomHandler.isFocusableElement(_el)
|
|
|
|
? DomHandler.isFocusableElement(_el, this.getComputedSelector(el.$_pfocustrap_focusableselector))
|
|
|
|
? _el
|
|
|
|
: DomHandler.getFirstFocusableElement(el, this.getComputedSelector(el.$_pfocustrap_focusableselector))
|
|
|
|
: DomHandler.getFirstFocusableElement(_el);
|
2023-07-03 22:20:35 +00:00
|
|
|
|
2023-11-15 18:30:43 +00:00
|
|
|
return ObjectUtils.isNotEmpty(focusableElement) ? focusableElement : _el.nextSibling && findNextFocusableElement(_el.nextSibling);
|
2023-07-03 22:20:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
},
|
|
|
|
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);
|
|
|
|
},
|
2024-01-21 11:56:17 +00:00
|
|
|
autoFocus(options) {
|
|
|
|
this.autoElementFocus(this.$el, { value: { ...options, autoFocus: true } });
|
|
|
|
},
|
|
|
|
autoElementFocus(el, binding) {
|
2023-07-03 22:20:35 +00:00
|
|
|
const { autoFocusSelector = '', firstFocusableSelector = '', autoFocus = false } = binding.value || {};
|
2023-07-03 23:12:09 +00:00
|
|
|
let focusableElement = DomHandler.getFirstFocusableElement(el, `[autofocus]${this.getComputedSelector(autoFocusSelector)}`);
|
2023-07-03 22:20:35 +00:00
|
|
|
|
2023-07-03 23:12:09 +00:00
|
|
|
autoFocus && !focusableElement && (focusableElement = DomHandler.getFirstFocusableElement(el, this.getComputedSelector(firstFocusableSelector)));
|
2023-07-03 22:20:35 +00:00
|
|
|
DomHandler.focus(focusableElement);
|
|
|
|
},
|
|
|
|
onFirstHiddenElementFocus(event) {
|
|
|
|
const { currentTarget, relatedTarget } = event;
|
|
|
|
const focusableElement =
|
2023-07-06 14:57:08 +00:00
|
|
|
relatedTarget === currentTarget.$_pfocustrap_lasthiddenfocusableelement || !this.$el?.contains(relatedTarget)
|
2023-07-03 23:12:09 +00:00
|
|
|
? DomHandler.getFirstFocusableElement(currentTarget.parentElement, this.getComputedSelector(currentTarget.$_pfocustrap_focusableselector))
|
2023-07-03 22:20:35 +00:00
|
|
|
: currentTarget.$_pfocustrap_lasthiddenfocusableelement;
|
|
|
|
|
|
|
|
DomHandler.focus(focusableElement);
|
|
|
|
},
|
|
|
|
onLastHiddenElementFocus(event) {
|
|
|
|
const { currentTarget, relatedTarget } = event;
|
|
|
|
const focusableElement =
|
2023-07-06 14:57:08 +00:00
|
|
|
relatedTarget === currentTarget.$_pfocustrap_firsthiddenfocusableelement || !this.$el?.contains(relatedTarget)
|
2023-07-03 23:12:09 +00:00
|
|
|
? DomHandler.getLastFocusableElement(currentTarget.parentElement, this.getComputedSelector(currentTarget.$_pfocustrap_focusableselector))
|
2023-07-03 22:20:35 +00:00
|
|
|
: currentTarget.$_pfocustrap_firsthiddenfocusableelement;
|
|
|
|
|
|
|
|
DomHandler.focus(focusableElement);
|
|
|
|
},
|
|
|
|
createHiddenFocusableElements(el, binding) {
|
|
|
|
const { tabIndex = 0, firstFocusableSelector = '', lastFocusableSelector = '' } = binding.value || {};
|
|
|
|
|
|
|
|
const createFocusableElement = (onFocus) => {
|
|
|
|
return DomHandler.createElement('span', {
|
|
|
|
class: 'p-hidden-accessible p-hidden-focusable',
|
|
|
|
tabIndex,
|
|
|
|
role: 'presentation',
|
|
|
|
'aria-hidden': true,
|
|
|
|
'data-p-hidden-accessible': true,
|
|
|
|
'data-p-hidden-focusable': true,
|
2023-07-03 23:12:09 +00:00
|
|
|
onFocus: onFocus?.bind(this)
|
2023-07-03 22:20:35 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const firstFocusableElement = createFocusableElement(this.onFirstHiddenElementFocus);
|
|
|
|
const lastFocusableElement = createFocusableElement(this.onLastHiddenElementFocus);
|
|
|
|
|
|
|
|
firstFocusableElement.$_pfocustrap_lasthiddenfocusableelement = lastFocusableElement;
|
|
|
|
firstFocusableElement.$_pfocustrap_focusableselector = firstFocusableSelector;
|
2023-07-07 12:00:11 +00:00
|
|
|
firstFocusableElement.setAttribute('data-pc-section', 'firstfocusableelement');
|
2023-07-03 22:20:35 +00:00
|
|
|
|
|
|
|
lastFocusableElement.$_pfocustrap_firsthiddenfocusableelement = firstFocusableElement;
|
|
|
|
lastFocusableElement.$_pfocustrap_focusableselector = lastFocusableSelector;
|
2023-07-07 12:00:11 +00:00
|
|
|
lastFocusableElement.setAttribute('data-pc-section', 'lastfocusableelement');
|
2023-07-03 22:20:35 +00:00
|
|
|
|
|
|
|
el.prepend(firstFocusableElement);
|
|
|
|
el.append(lastFocusableElement);
|
|
|
|
}
|
2022-12-08 11:04:25 +00:00
|
|
|
}
|
2023-06-22 13:53:04 +00:00
|
|
|
});
|
2022-12-08 11:04:25 +00:00
|
|
|
|
|
|
|
export default FocusTrap;
|