1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 10:54:44 +00:00

feat(packages/platform): add focusGuard brwoser util

This commit is contained in:
2024-11-20 16:49:24 +07:00
parent 979fd6e6df
commit 50257463b7
3 changed files with 120 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { focusGuard, createGuardAttrs } from '.';
describe('focusGuard', () => {
beforeEach(() => {
document.body.innerHTML = '';
});
it('initialize with the correct default namespace', () => {
const guard = focusGuard();
expect(guard.selector).toBe('data-focus-guard');
});
it('create focus guards in the DOM', () => {
const guard = focusGuard();
guard.createGuard();
const guards = document.querySelectorAll(`[${guard.selector}]`);
expect(guards.length).toBe(2);
guards.forEach((element) => {
expect(element.tagName).toBe('SPAN');
expect(element.getAttribute('tabindex')).toBe('0');
});
});
it('remove focus guards from the DOM correctly', () => {
const guard = focusGuard();
guard.createGuard();
guard.removeGuard();
const guards = document.querySelectorAll(`[${guard.selector}]`);
expect(guards.length).toBe(0);
});
it('reuse the same guards when calling createGuard multiple times', () => {
const guard = focusGuard();
guard.createGuard();
guard.createGuard();
guard.removeGuard();
const guards = document.querySelectorAll(`[${guard.selector}]`);
expect(guards.length).toBe(0);
});
it('allow custom namespaces', () => {
const namespace = 'custom-guard';
const guard = focusGuard(namespace);
guard.createGuard();
expect(guard.selector).toBe(`data-${namespace}`);
const guards = document.querySelectorAll(`[${guard.selector}]`);
expect(guards.length).toBe(2);
});
it('createGuardAttrs should create a valid guard element', () => {
const namespace = 'custom-guard';
const element = createGuardAttrs(namespace);
expect(element.tagName).toBe('SPAN');
expect(element.getAttribute(namespace)).toBe('');
expect(element.getAttribute('tabindex')).toBe('0');
expect(element.getAttribute('style')).toBe('outline: none; opacity: 0; pointer-events: none; position: fixed;');
});
});

View File

@@ -0,0 +1,50 @@
/**
* @name focusGuard
* @category Browsers
* @description Adds a pair of focus guards at the boundaries of the DOM tree to ensure consistent focus behavior
*
* @param {string} namespace - The namespace to use for the guard attributes
* @returns {Object} - An object containing the selector, createGuard, and removeGuard functions
*
* @example
* const guard = focusGuard();
* guard.createGuard();
* guard.removeGuard();
*
* @example
* const guard = focusGuard('focus-guard');
* guard.createGuard();
* guard.removeGuard();
*
* @since 0.0.3
*/
export function focusGuard(namespace: string = 'focus-guard') {
const guardAttr = `data-${namespace}`;
const createGuard = () => {
const edges = document.querySelectorAll(`[${guardAttr}]`);
document.body.insertAdjacentElement('afterbegin', edges[0] ?? createGuardAttrs(guardAttr));
document.body.insertAdjacentElement('beforeend', edges[1] ?? createGuardAttrs(guardAttr));
};
const removeGuard = () => {
document.querySelectorAll(`[${guardAttr}]`).forEach((element) => element.remove());
};
return {
selector: guardAttr,
createGuard,
removeGuard,
};
}
export function createGuardAttrs(namespace: string) {
const element = document.createElement('span');
element.setAttribute(namespace, '');
element.setAttribute('tabindex', '0');
element.setAttribute('style', 'outline: none; opacity: 0; pointer-events: none; position: fixed;');
return element;
}

View File

@@ -0,0 +1 @@
export * from './focusGuard';