@mkmonkeycat/dom-utils 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.github/workflows/release.yml +39 -0
- package/.prettierignore +1 -0
- package/.prettierrc.yml +9 -0
- package/dist/dom/element.d.ts +104 -0
- package/dist/dom/events.d.ts +88 -0
- package/dist/dom/index.d.ts +5 -0
- package/dist/dom/observer.d.ts +66 -0
- package/dist/dom/style.d.ts +14 -0
- package/dist/dom/tag-internal.d.ts +13 -0
- package/dist/dom/win.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/main.d.ts +1 -0
- package/dist/mkdomutils.cjs.js +1 -0
- package/dist/mkdomutils.iife.js +1 -0
- package/dist/mkdomutils.js +283 -0
- package/dist/mkdomutils.umd.js +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/string.d.ts +12 -0
- package/dist/utils/tag.d.ts +20 -0
- package/dist/utils/timing.d.ts +58 -0
- package/dist/utils/types.d.ts +12 -0
- package/eslint.config.mjs +33 -0
- package/index.html +13 -0
- package/package.json +55 -0
- package/src/dom/element.ts +252 -0
- package/src/dom/events.ts +245 -0
- package/src/dom/index.ts +5 -0
- package/src/dom/observer.ts +197 -0
- package/src/dom/style.ts +23 -0
- package/src/dom/tag-internal.ts +19 -0
- package/src/dom/win.ts +21 -0
- package/src/index.ts +4 -0
- package/src/main.ts +0 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/string.ts +17 -0
- package/src/utils/tag.ts +31 -0
- package/src/utils/timing.ts +179 -0
- package/src/utils/types.ts +14 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +21 -0
- package/vitest.config.ts +12 -0
package/src/dom/style.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects a <style> tag with the provided CSS into the document.
|
|
3
|
+
* @param css - The CSS text to inject.
|
|
4
|
+
* @param options - Optional configuration such as `id`, `nonce`, and target container.
|
|
5
|
+
* @returns An object containing the created style element and a `remove` function.
|
|
6
|
+
*/
|
|
7
|
+
export const injectStyle = (
|
|
8
|
+
css: string,
|
|
9
|
+
options: { id?: string; nonce?: string; target?: HTMLElement } = {},
|
|
10
|
+
): { style: HTMLStyleElement; remove: () => void } => {
|
|
11
|
+
const { id, nonce, target = document.head } = options;
|
|
12
|
+
const style = document.createElement('style');
|
|
13
|
+
if (id) style.id = id;
|
|
14
|
+
if (nonce) style.setAttribute('nonce', nonce);
|
|
15
|
+
style.textContent = css;
|
|
16
|
+
target.appendChild(style);
|
|
17
|
+
|
|
18
|
+
const remove = () => {
|
|
19
|
+
style.parentNode?.removeChild(style);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return { style, remove };
|
|
23
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isMarkedAs, markFunc, type TaggedFunction } from '../utils/tag';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Marks a function as an mk utils internal event handler.
|
|
5
|
+
* @param func - The function to mark.
|
|
6
|
+
* @returns The marked function.
|
|
7
|
+
*/
|
|
8
|
+
export const markAsEventFunc = <F extends CallableFunction>(
|
|
9
|
+
func: F,
|
|
10
|
+
): TaggedFunction<F, '__mkEvent'> => markFunc(func, '__mkEvent');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a function is marked as an mk utils internal event handler.
|
|
14
|
+
* @param func - The function to check.
|
|
15
|
+
* @returns True if the function is marked as an event handler, false otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export const isEventFunc = <F extends CallableFunction>(
|
|
18
|
+
func: F,
|
|
19
|
+
): func is TaggedFunction<F, '__mkEvent'> => isMarkedAs(func, '__mkEvent');
|
package/src/dom/win.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const globalWin: Win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
|
|
2
|
+
export const globalDoc: Document = globalWin.document;
|
|
3
|
+
|
|
4
|
+
export function getWin(): Win;
|
|
5
|
+
export function getWin(
|
|
6
|
+
target: Node | Event | ShadowRoot | Document | Window | null | undefined,
|
|
7
|
+
): Win | null;
|
|
8
|
+
export function getWin(target?: Node | Event | ShadowRoot | Document | Window | null): Win | null {
|
|
9
|
+
if (!target) return globalWin;
|
|
10
|
+
|
|
11
|
+
if (target instanceof Window) return target as Win;
|
|
12
|
+
if (target instanceof UIEvent && target.view) return target.view as Win;
|
|
13
|
+
if (target instanceof Document) return target.defaultView;
|
|
14
|
+
if (target instanceof ShadowRoot) return target.host.ownerDocument?.defaultView ?? null;
|
|
15
|
+
if (target instanceof Node) return target.ownerDocument?.defaultView ?? null;
|
|
16
|
+
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type Win = Window & typeof globalThis;
|
|
21
|
+
declare const unsafeWindow: Win | undefined;
|
package/src/index.ts
ADDED
package/src/main.ts
ADDED
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a camelCase string to kebab-case.
|
|
3
|
+
* @param str - The camelCase string to convert.
|
|
4
|
+
* @returns The kebab-case string.
|
|
5
|
+
*/
|
|
6
|
+
export const camelToKebab = (str: string): string => {
|
|
7
|
+
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a PascalCase string to kebab-case.
|
|
12
|
+
* @param str - The PascalCase string to convert.
|
|
13
|
+
* @returns The kebab-case string.
|
|
14
|
+
*/
|
|
15
|
+
export const pascalToKebab = (str: string): string => {
|
|
16
|
+
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
|
|
17
|
+
};
|
package/src/utils/tag.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type representing a function tagged with a specific string.
|
|
3
|
+
*/
|
|
4
|
+
export type TaggedFunction<F extends CallableFunction, T extends string> = F & {
|
|
5
|
+
readonly [K in T]?: true;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Marks a function with a specific tag.
|
|
10
|
+
* @param func - The function to mark.
|
|
11
|
+
* @param tag - The tag to assign.
|
|
12
|
+
* @returns The marked function.
|
|
13
|
+
*/
|
|
14
|
+
export const markFunc = <F extends CallableFunction, T extends string>(
|
|
15
|
+
func: F,
|
|
16
|
+
tag: T,
|
|
17
|
+
): TaggedFunction<F, T> => {
|
|
18
|
+
Object.defineProperty(func, tag, { value: true });
|
|
19
|
+
return func as TaggedFunction<F, T>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a function is marked with a specific tag.
|
|
24
|
+
* @param func - The function to check.
|
|
25
|
+
* @param tag - The tag to look for.
|
|
26
|
+
* @returns True if the function is marked with the tag, false otherwise.
|
|
27
|
+
*/
|
|
28
|
+
export const isMarkedAs = <F extends CallableFunction, T extends string>(
|
|
29
|
+
func: F,
|
|
30
|
+
tag: T,
|
|
31
|
+
): func is TaggedFunction<F, T> => !!(func as Record<string, unknown>)[tag];
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debounce function that delays the execution of a callback until after a specified wait time has elapsed.
|
|
3
|
+
* @param callback - The function to debounce.
|
|
4
|
+
* @param wait - The delay in milliseconds.
|
|
5
|
+
* @returns A debounced function with cancel and flush methods.
|
|
6
|
+
*/
|
|
7
|
+
export const debounce = <T extends (...args: unknown[]) => unknown>(
|
|
8
|
+
callback: T,
|
|
9
|
+
wait: number,
|
|
10
|
+
): ((...args: Parameters<T>) => void) & { cancel: () => void; flush: () => void } => {
|
|
11
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
12
|
+
let lastArgs: Parameters<T> | null = null;
|
|
13
|
+
|
|
14
|
+
const debounced = (...args: Parameters<T>) => {
|
|
15
|
+
lastArgs = args;
|
|
16
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
17
|
+
timeoutId = setTimeout(() => {
|
|
18
|
+
callback(...lastArgs!);
|
|
19
|
+
timeoutId = null;
|
|
20
|
+
lastArgs = null;
|
|
21
|
+
}, wait);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
debounced.cancel = () => {
|
|
25
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
26
|
+
timeoutId = null;
|
|
27
|
+
lastArgs = null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
debounced.flush = () => {
|
|
31
|
+
if (timeoutId) {
|
|
32
|
+
callback(...lastArgs!);
|
|
33
|
+
clearTimeout(timeoutId);
|
|
34
|
+
timeoutId = null;
|
|
35
|
+
lastArgs = null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return debounced;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Throttle function that executes a callback at most once every specified wait time.
|
|
44
|
+
* @param callback - The function to throttle.
|
|
45
|
+
* @param wait - The minimum interval in milliseconds between executions.
|
|
46
|
+
* @returns A throttled function with cancel and flush methods.
|
|
47
|
+
*/
|
|
48
|
+
export const throttle = <T extends (...args: unknown[]) => unknown>(
|
|
49
|
+
callback: T,
|
|
50
|
+
wait: number,
|
|
51
|
+
): ((...args: Parameters<T>) => void) & { cancel: () => void; flush: () => void } => {
|
|
52
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
53
|
+
let lastArgs: Parameters<T> | null = null;
|
|
54
|
+
let lastCallTime = 0;
|
|
55
|
+
|
|
56
|
+
const cleanup = () => {
|
|
57
|
+
if (timeoutId) {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
timeoutId = null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const throttled = (...args: Parameters<T>) => {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
lastArgs = args;
|
|
66
|
+
|
|
67
|
+
if (lastCallTime === 0) {
|
|
68
|
+
callback(...args);
|
|
69
|
+
lastCallTime = now;
|
|
70
|
+
} else {
|
|
71
|
+
const elapsed = now - lastCallTime;
|
|
72
|
+
if (elapsed >= wait) {
|
|
73
|
+
callback(...args);
|
|
74
|
+
lastCallTime = now;
|
|
75
|
+
cleanup();
|
|
76
|
+
} else {
|
|
77
|
+
cleanup();
|
|
78
|
+
timeoutId = setTimeout(() => {
|
|
79
|
+
callback(...lastArgs!);
|
|
80
|
+
lastCallTime = Date.now();
|
|
81
|
+
timeoutId = null;
|
|
82
|
+
}, wait - elapsed);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
throttled.cancel = () => {
|
|
88
|
+
cleanup();
|
|
89
|
+
lastCallTime = 0;
|
|
90
|
+
lastArgs = null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
throttled.flush = () => {
|
|
94
|
+
if (lastCallTime > 0 && lastArgs !== null) {
|
|
95
|
+
callback(...lastArgs);
|
|
96
|
+
}
|
|
97
|
+
cleanup();
|
|
98
|
+
lastCallTime = 0;
|
|
99
|
+
lastArgs = null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return throttled;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a promise that resolves after a specified delay.
|
|
107
|
+
* @param ms - The delay in milliseconds.
|
|
108
|
+
* @returns A promise that resolves after the delay.
|
|
109
|
+
*/
|
|
110
|
+
export const delay = (ms: number): Promise<void> => {
|
|
111
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Pauses execution for a specified duration in async contexts.
|
|
116
|
+
* @param ms - The duration to sleep in milliseconds.
|
|
117
|
+
* @returns A promise that resolves after the sleep duration.
|
|
118
|
+
*/
|
|
119
|
+
export const sleep = (ms: number): Promise<void> => delay(ms);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Creates a promise that rejects if the given promise doesn't resolve within the specified timeout.
|
|
123
|
+
* @param promise - The promise to apply a timeout to.
|
|
124
|
+
* @param ms - The timeout duration in milliseconds.
|
|
125
|
+
* @param message - Optional error message for the timeout error.
|
|
126
|
+
* @returns A promise that resolves with the original promise's value or rejects on timeout.
|
|
127
|
+
*/
|
|
128
|
+
export const timeout = <T>(
|
|
129
|
+
promise: Promise<T>,
|
|
130
|
+
ms: number,
|
|
131
|
+
message = `Operation timed out after ${ms}ms`,
|
|
132
|
+
): Promise<T> => {
|
|
133
|
+
return Promise.race([
|
|
134
|
+
promise,
|
|
135
|
+
new Promise<T>((_, reject) => setTimeout(() => reject(new Error(message)), ms)),
|
|
136
|
+
]);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A no-operation function that does nothing when called.
|
|
141
|
+
*/
|
|
142
|
+
export const noop = (): void => {};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Retries a function until it succeeds or reaches the maximum number of attempts.
|
|
146
|
+
* @param fn - The async function to retry.
|
|
147
|
+
* @param options - Configuration options for retry behavior.
|
|
148
|
+
* @returns A promise that resolves with the function result or rejects if all attempts fail.
|
|
149
|
+
*/
|
|
150
|
+
export const retry = async <T>(
|
|
151
|
+
fn: () => Promise<T>,
|
|
152
|
+
options: {
|
|
153
|
+
/** Maximum number of attempts. Default: 3 */
|
|
154
|
+
maxAttempts?: number;
|
|
155
|
+
/** Delay in milliseconds between attempts. Default: 1000 */
|
|
156
|
+
delay?: number;
|
|
157
|
+
/** Whether to use exponential backoff. Default: false */
|
|
158
|
+
exponential?: boolean;
|
|
159
|
+
} = {},
|
|
160
|
+
): Promise<T> => {
|
|
161
|
+
const { maxAttempts = 3, delay = 1000, exponential = false } = options;
|
|
162
|
+
|
|
163
|
+
let lastError: Error | undefined;
|
|
164
|
+
|
|
165
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
166
|
+
try {
|
|
167
|
+
return await fn();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
170
|
+
|
|
171
|
+
if (attempt < maxAttempts) {
|
|
172
|
+
const waitTime = exponential ? delay * Math.pow(2, attempt - 1) : delay;
|
|
173
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
throw lastError || new Error('Retry failed');
|
|
179
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type that represents a value that can be either a single instance of type T or an array of type T.
|
|
3
|
+
*/
|
|
4
|
+
export type SingleOrArray<T> = T | T[];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A type that represents a value that can be either a direct value of type T or a Promise that resolves to type T.
|
|
8
|
+
*/
|
|
9
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A type that represents a value that can be null or undefined.
|
|
13
|
+
*/
|
|
14
|
+
export type Nullable<T> = T | null | undefined;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"types": ["vite/client"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src"]
|
|
26
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { defineConfig } from 'vite';
|
|
4
|
+
import dts from 'vite-plugin-dts';
|
|
5
|
+
|
|
6
|
+
const name = 'MKDomUtils';
|
|
7
|
+
const fileName = name.toLowerCase();
|
|
8
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
build: {
|
|
12
|
+
lib: {
|
|
13
|
+
name,
|
|
14
|
+
entry: resolve(__dirname, 'src/index.ts'),
|
|
15
|
+
formats: ['es', 'cjs', 'umd', 'iife'],
|
|
16
|
+
fileName: (format) => `${fileName}${format.includes('es') ? '' : `.${format}`}.js`,
|
|
17
|
+
},
|
|
18
|
+
rollupOptions: { output: { assetFileNames: () => `${fileName}[extname]` } },
|
|
19
|
+
},
|
|
20
|
+
plugins: [dts()],
|
|
21
|
+
});
|
package/vitest.config.ts
ADDED