@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
108 lines (107 loc) • 4.41 kB
JavaScript
;
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const common_utils = require("../utils.cjs");
const focusableAttrs = ':not(:disabled):not([aria-disabled="true"]):not([role="presentation"])';
const tabbableAttrs = `${focusableAttrs}:not([tabindex="-1"])`;
const focusableElementsList = `button,[href],input,select,textarea,details,[tabindex]`;
const Modal = {
methods: {
/**
* get the first focusable element in your component, includes tabindex="-1".
* @param {object} el - optional - ref of dom element to trap focus on.
* will default to the root node of the vue component
*/
async getFirstFocusableElement(el) {
await this.$nextTick();
const focusableElements = this._getFocusableElements(el, true);
return this._getFirstFocusElement(focusableElements);
},
/**
* set focus to the first focusable element in your component, includes tabindex="-1".
* @param {object} el - optional - ref of dom element to trap focus on.
* will default to the root node of the vue component
*/
async focusFirstElement(el = this.$el) {
const elToFocus = await this.getFirstFocusableElement(common_utils.returnFirstEl(el));
elToFocus == null ? void 0 : elToFocus.focus({ preventScroll: true });
},
async focusElementById(elementId) {
var _a;
await this.$nextTick();
const result = (_a = common_utils.returnFirstEl(this.$el)) == null ? void 0 : _a.querySelector(elementId);
if (result) {
result.focus();
return;
}
console.warn('Could not find the element specified in dt-modal prop "initialFocusElement". Defaulting to focusing the first element.');
await this.focusFirstElement();
},
/**
* internal use only.
*
* @param focusableElements - list of focusable elements
* @returns {*} - first DOM element that is focusable.
* @private
*/
_getFirstFocusElement(focusableElements) {
if (!focusableElements.length) {
return;
}
let firstFocusEl = focusableElements[0];
if (firstFocusEl.matches('[type="radio"]:not(:checked)')) {
firstFocusEl = focusableElements.find((el) => el.checked && el.name === firstFocusEl.name) || firstFocusEl;
}
return firstFocusEl;
},
/**
* internal use only.
*
* gets all the focusable elements within the component
* and sets the first and last of those elements.
*
* @param {object} el - the root dom element to find focusable elements in.
* @param {bool} includeNegativeTabIndex - will include tabindex="-1" in the list of focusable elements.
*/
_getFocusableElements(el = this.$el, includeNegativeTabIndex = false) {
el = common_utils.returnFirstEl(el);
if (!el) return [];
const focusableContent = [...el.querySelectorAll(focusableElementsList)];
return focusableContent.filter((fc) => {
const style = window.getComputedStyle(fc);
return style.getPropertyValue("display") !== "none" && style.getPropertyValue("visibility") !== "hidden" && fc.matches(includeNegativeTabIndex ? focusableAttrs : tabbableAttrs);
});
},
/**
* tabs to the next element contained within your component, does not include tabindex="-1".
* @param {object} e - keypress event
* @param {object} el - optional - ref of dom element to trap focus on.
* will default to the root node of the vue component
*/
focusTrappedTabPress(e, el) {
const isTabPressed = e.key === "Tab";
if (!isTabPressed) {
return;
}
const focusableElements = this._getFocusableElements(el);
if (!focusableElements.length) {
e.preventDefault();
return;
}
const firstFocusableElement = this._getFirstFocusElement(focusableElements);
const lastFocusableElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey) {
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus();
e.preventDefault();
}
} else {
if (document.activeElement === lastFocusableElement) {
firstFocusableElement.focus();
e.preventDefault();
}
}
}
}
};
exports.default = Modal;
//# sourceMappingURL=modal.cjs.map