Skip to content

Commit

Permalink
Refactor #3965 - For FocusTrap
Browse files Browse the repository at this point in the history
  • Loading branch information
tugcekucukoglu committed Jun 22, 2023
1 parent eea680f commit a6e8c4d
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 7 deletions.
93 changes: 93 additions & 0 deletions components/lib/focustrap/FocusTrap.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,89 @@
*/
import { DirectiveBinding, ObjectDirective } from 'vue';

/**
* Custom passthrough(pt) hooks options.
*/
export interface FocusTrapPassThroughHooksOptions {
/**
* Called before bound element's attributes or event listeners are applied.
*/
created?: DirectiveBinding;
/**
* Called right before the element is inserted into the DOM.
*/
beforeMount?: DirectiveBinding;
/**
* Called when the bound element's parent component and all its children are mounted.
*/
mounted?: DirectiveBinding;
/**
* Called before the parent component is updated.
*/
beforeUpdate?: DirectiveBinding;
/**
* Called after the parent component and all of its children have updated all of its children have updated.
*/
updated?: DirectiveBinding;
/**
* Called before the parent component is unmounted.
*/
beforeUnmount?: DirectiveBinding;
/**
* Called when the parent component is unmounted.
*/
unmounted?: DirectiveBinding;
}

/**
* Custom passthrough(pt) css options.
*/
export interface FocusTrapPassThroughCSSOptions {
/**
* Style class of the element.
*/
class?: any;
/**
* Inline style of the element.
*/
style?: any;
}

export interface FocusTrapPassThroughDirectiveOptions {
/**
* Uses to pass attributes to the life cycle hooks.
* @see {@link FocusTrapPassThroughHooksOptions}
*/
hooks?: FocusTrapPassThroughHooksOptions;
/**
* Uses to pass attributes to the styles.
* @see {@link FocusTrapPassThroughCSSOptions}
*/
css?: FocusTrapPassThroughCSSOptions;
}

/**
* Custom passthrough(pt) options.
* @see {@link FocusTrapOptions.pt}
*/
export interface FocusTrapPassThroughOptions {
/**
* Uses to pass attributes to the root's DOM element.
* @see {@link FocusTrapPassThroughDirectiveOptions}
*/
root?: FocusTrapPassThroughDirectiveOptions;
/**
* Uses to pass attributes to the first focusable element's DOM element.
* @see {@link FocusTrapPassThroughDirectiveOptions}
*/
firstFocusableElement?: FocusTrapPassThroughDirectiveOptions;
/**
* Uses to pass attributes to the last focusable element's DOM element.
* @see {@link FocusTrapPassThroughDirectiveOptions}
*/
lastFocusableElement?: FocusTrapPassThroughDirectiveOptions;
}

/**
* Defines options of FocusTrap.
*/
Expand All @@ -17,6 +100,16 @@ export interface FocusTrapOptions {
* @defaultValue false
*/
disabled?: boolean | undefined;
/**
* When When disabled, focustrap will not focus by default.
* @defaultValue true
*/
autoFocus?: boolean | undefined;
/**
* Uses to pass attributes to DOM elements inside the component.
* @type {FocusTrapPassThroughOptions}
*/
pt?: FocusTrapPassThroughOptions;
}

/**
Expand Down
38 changes: 31 additions & 7 deletions components/lib/focustrap/FocusTrap.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BaseDirective } from 'primevue/basedirective';
import { DomHandler, ObjectUtils } from 'primevue/utils';

function bind(el, binding) {
Expand Down Expand Up @@ -37,17 +38,17 @@ function unbind(el) {

function autoFocus(el, binding) {
const { autoFocusSelector = '', firstFocusableSelector = '', autoFocus = false } = binding.value || {};
let focusableElement = DomHandler.getFirstFocusableElement(el, `[autofocus]:not(.p-hidden-focusable)${autoFocusSelector}`);
let focusableElement = DomHandler.getFirstFocusableElement(el, `[autofocus]:not([data-p-hidden-focusable="true"])${autoFocusSelector}`);

autoFocus && !focusableElement && (focusableElement = DomHandler.getFirstFocusableElement(el, `:not(.p-hidden-focusable)${firstFocusableSelector}`));
autoFocus && !focusableElement && (focusableElement = DomHandler.getFirstFocusableElement(el, `:not([data-p-hidden-focusable="true"])${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}`)
? DomHandler.getFirstFocusableElement(currentTarget.parentElement, `:not([data-p-hidden-focusable="true"])${currentTarget.$_pfocustrap_focusableselector}`)
: currentTarget.$_pfocustrap_lasthiddenfocusableelement;

DomHandler.focus(focusableElement);
Expand All @@ -57,7 +58,7 @@ function onLastHiddenElementFocus(event) {
const { currentTarget, relatedTarget } = event;
const focusableElement =
relatedTarget === currentTarget.$_pfocustrap_firsthiddenfocusableelement
? DomHandler.getLastFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
? DomHandler.getLastFocusableElement(currentTarget.parentElement, `:not([data-p-hidden-focusable="true"])${currentTarget.$_pfocustrap_focusableselector}`)
: currentTarget.$_pfocustrap_firsthiddenfocusableelement;

DomHandler.focus(focusableElement);
Expand All @@ -69,10 +70,24 @@ function createHiddenFocusableElements(el, binding) {
const createFocusableElement = (onFocus) => {
const element = document.createElement('span');

element.classList.value = 'p-hidden-accessible p-hidden-focusable';
if (binding.instance.$primevue && binding.instance.$primevue.config && binding.instance.$primevue.config.unstyled) {
element.style.border = '0';
element.style.clip = 'rect(0 0 0 0)';
element.style.height = '1px';
element.style.margin = '-1px';
element.style.overflow = 'hidden';
element.style.padding = '0';
element.style.position = 'absolute';
element.style.width = '1px';
} else {
element.classList = 'p-hidden-accessible p-hidden-focusable';
}

element.tabIndex = tabIndex;
element.setAttribute('aria-hidden', 'true');
element.setAttribute('role', 'presentation');
element.setAttribute('data-p-hidden-accessible', true);
element.setAttribute('data-p-hidden-focusable', true);
element.addEventListener('focus', onFocus);

return element;
Expand All @@ -83,15 +98,17 @@ function createHiddenFocusableElements(el, binding) {

firstFocusableElement.$_pfocustrap_lasthiddenfocusableelement = lastFocusableElement;
firstFocusableElement.$_pfocustrap_focusableselector = firstFocusableSelector;
firstFocusableElement.setAttribute('data-pc-section', 'firstfocusableelement');

lastFocusableElement.$_pfocustrap_firsthiddenfocusableelement = firstFocusableElement;
lastFocusableElement.$_pfocustrap_focusableselector = lastFocusableSelector;
lastFocusableElement.setAttribute('data-pc-section', 'lastfocusableelement');

el.prepend(firstFocusableElement);
el.append(lastFocusableElement);
}

const FocusTrap = {
const FocusTrap = BaseDirective.extend('focustrap', {
mounted(el, binding) {
const { disabled } = binding.value || {};

Expand All @@ -100,7 +117,14 @@ const FocusTrap = {
bind(el, binding);
autoFocus(el, binding);
}

el.setAttribute('data-pc-section', 'root');
el.setAttribute('data-pc-name', 'focustrap');

BaseDirective.directiveElement = el;
BaseDirective.handleCSS('focustrap', el, binding);
},

updated(el, binding) {
const { disabled } = binding.value || {};

Expand All @@ -109,6 +133,6 @@ const FocusTrap = {
unmounted(el) {
unbind(el);
}
};
});

export default FocusTrap;

0 comments on commit a6e8c4d

Please sign in to comment.