mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
refactor: change separate tools by category
This commit is contained in:
69
core/platform/src/browsers/focusGuard/index.test.ts
Normal file
69
core/platform/src/browsers/focusGuard/index.test.ts
Normal 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;');
|
||||
});
|
||||
});
|
||||
50
core/platform/src/browsers/focusGuard/index.ts
Normal file
50
core/platform/src/browsers/focusGuard/index.ts
Normal 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;
|
||||
}
|
||||
1
core/platform/src/browsers/index.ts
Normal file
1
core/platform/src/browsers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './focusGuard';
|
||||
47
core/platform/src/multi/debounce/index.ts
Normal file
47
core/platform/src/multi/debounce/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export interface DebounceOptions {
|
||||
/**
|
||||
* Call the function on the leading edge of the timeout, instead of waiting for the trailing edge
|
||||
*/
|
||||
readonly immediate?: boolean;
|
||||
|
||||
/**
|
||||
* Call the function on the trailing edge with the last used arguments.
|
||||
* Result of call is from previous call
|
||||
*/
|
||||
readonly trailing?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_DEBOUNCE_OPTIONS: DebounceOptions = {
|
||||
trailing: true,
|
||||
}
|
||||
|
||||
export function debounce<FnArguments extends unknown[], FnReturn>(
|
||||
fn: (...args: FnArguments) => PromiseLike<FnReturn> | FnReturn,
|
||||
timeout: number = 20,
|
||||
options: DebounceOptions = {},
|
||||
) {
|
||||
options = {
|
||||
...DEFAULT_DEBOUNCE_OPTIONS,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (!Number.isFinite(timeout) || timeout <= 0)
|
||||
throw new TypeError('Debounce timeout must be a positive number');
|
||||
|
||||
// Last result for leading edge
|
||||
let leadingValue: PromiseLike<FnReturn> | FnReturn;
|
||||
|
||||
// Debounce timeout id
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
|
||||
// Promises to be resolved when debounce is finished
|
||||
let resolveList: Array<(value: unknown) => void> = [];
|
||||
|
||||
// State of currently resolving promise
|
||||
let currentResolve: Promise<FnReturn>;
|
||||
|
||||
// Trailing call information
|
||||
let trailingArgs: unknown[];
|
||||
|
||||
|
||||
}
|
||||
28
core/platform/src/multi/global/index.ts
Normal file
28
core/platform/src/multi/global/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// TODO: tests
|
||||
|
||||
/**
|
||||
* @name _global
|
||||
* @category Multi
|
||||
* @description Global object that works in any environment
|
||||
*
|
||||
* @since 0.0.1
|
||||
*/
|
||||
export const _global =
|
||||
typeof globalThis !== 'undefined'
|
||||
? globalThis
|
||||
: typeof window !== 'undefined'
|
||||
? window
|
||||
: typeof global !== 'undefined'
|
||||
? global
|
||||
: typeof self !== 'undefined'
|
||||
? self
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
* @name isClient
|
||||
* @category Multi
|
||||
* @description Check if the current environment is the client
|
||||
*
|
||||
* @since 0.0.1
|
||||
*/
|
||||
export const isClient = typeof window !== 'undefined' && typeof document !== 'undefined';
|
||||
2
core/platform/src/multi/index.ts
Normal file
2
core/platform/src/multi/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './global';
|
||||
// export * from './debounce';
|
||||
Reference in New Issue
Block a user