@mkmonkeycat/dom-utils 0.1.0 → 1.0.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/src/dom/events.ts DELETED
@@ -1,245 +0,0 @@
1
- import type { SingleOrArray } from '../utils/types';
2
- import { markAsEventFunc } from './tag-internal';
3
-
4
- /**
5
- * Options for onClickOutside function.
6
- */
7
- export interface OnClickOutsideOptions {
8
- /** Event type to listen for. Default: 'mousedown' */
9
- event?: 'mousedown' | 'mouseup' | 'click';
10
- /** Whether to capture the event. Default: true */
11
- capture?: boolean;
12
- /** Elements to exclude from the outside check */
13
- exclude?: (HTMLElement | null)[];
14
- }
15
-
16
- /**
17
- * Attaches a listener that triggers when clicking outside the specified element.
18
- * @param element - The element to watch.
19
- * @param callback - The callback to invoke when clicking outside.
20
- * @param options - Configuration options.
21
- * @returns A function to remove the listener.
22
- */
23
- export const onClickOutside = (
24
- element: HTMLElement,
25
- callback: (event: MouseEvent) => void,
26
- options: OnClickOutsideOptions = {},
27
- ): (() => void) => {
28
- const { event = 'mousedown', capture = true, exclude = [] } = options;
29
-
30
- const handleClick = markAsEventFunc((e: MouseEvent) => {
31
- const target = e.target as Node;
32
-
33
- // Check if click is on the element or its descendants
34
- if (element.contains(target)) return;
35
-
36
- // Check if click is on excluded elements
37
- if (exclude.some((el) => el?.contains(target))) return;
38
-
39
- callback(e);
40
- });
41
-
42
- document.addEventListener(event, handleClick, capture);
43
-
44
- return () => document.removeEventListener(event, handleClick, capture);
45
- };
46
-
47
- /**
48
- * Options for onKeyPress function.
49
- */
50
- export interface OnKeyPressOptions {
51
- /** Whether to prevent default behavior. Default: false */
52
- preventDefault?: boolean;
53
- /** Whether to stop propagation. Default: false */
54
- stopPropagation?: boolean;
55
- /** Event type. Default: 'keydown' */
56
- event?: 'keydown' | 'keyup' | 'keypress';
57
- /** Target element. Default: document */
58
- target?: HTMLElement | Document;
59
- }
60
-
61
- /**
62
- * Attaches a listener for specific key presses.
63
- * @param key - The key or keys to listen for (e.g., 'Escape', 'Enter', ['a', 'b']).
64
- * @param callback - The callback to invoke when the key is pressed.
65
- * @param options - Configuration options.
66
- * @returns A function to remove the listener.
67
- */
68
- export const onKeyPress = (
69
- key: string | string[],
70
- callback: (event: KeyboardEvent) => void,
71
- options: OnKeyPressOptions = {},
72
- ): (() => void) => {
73
- const {
74
- preventDefault = false,
75
- stopPropagation = false,
76
- event = 'keydown',
77
- target = document,
78
- } = options;
79
-
80
- const keys = Array.isArray(key) ? key : [key];
81
-
82
- const handleKeyPress = markAsEventFunc((e: KeyboardEvent) => {
83
- if (keys.includes(e.key)) {
84
- if (preventDefault) e.preventDefault();
85
- if (stopPropagation) e.stopPropagation();
86
- callback(e);
87
- }
88
- });
89
-
90
- target.addEventListener(event, handleKeyPress as EventListener);
91
-
92
- return () => target.removeEventListener(event, handleKeyPress as EventListener);
93
- };
94
-
95
- /**
96
- * Options for onEscape function.
97
- */
98
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
99
- export interface OnEscapeOptions extends Omit<OnKeyPressOptions, 'event'> {}
100
-
101
- /**
102
- * Attaches a listener for the Escape key.
103
- * @param callback - The callback to invoke when Escape is pressed.
104
- * @param options - Configuration options.
105
- * @returns A function to remove the listener.
106
- */
107
- export const onEscape = (
108
- callback: (event: KeyboardEvent) => void,
109
- options: OnEscapeOptions = {},
110
- ): (() => void) => {
111
- return onKeyPress('Escape', callback, options);
112
- };
113
-
114
- /**
115
- * Options for onEnter function.
116
- */
117
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
118
- export interface OnEnterOptions extends Omit<OnKeyPressOptions, 'event'> {}
119
-
120
- /**
121
- * Attaches a listener for the Enter key.
122
- * @param callback - The callback to invoke when Enter is pressed.
123
- * @param options - Configuration options.
124
- * @returns A function to remove the listener.
125
- */
126
- export const onEnter = (
127
- callback: (event: KeyboardEvent) => void,
128
- options: OnEnterOptions = {},
129
- ): (() => void) => {
130
- return onKeyPress('Enter', callback, options);
131
- };
132
-
133
- /**
134
- * Options for onLongPress function.
135
- */
136
- export interface OnLongPressOptions {
137
- /** Duration in milliseconds to trigger long press. Default: 500 */
138
- duration?: number;
139
- /** Threshold in pixels for movement tolerance. Default: 10 */
140
- threshold?: number;
141
- }
142
-
143
- /**
144
- * Attaches a listener for long press events.
145
- * @param element - The element to watch.
146
- * @param callback - The callback to invoke on long press.
147
- * @param options - Configuration options.
148
- * @returns A function to remove the listeners.
149
- */
150
- export const onLongPress = (
151
- element: HTMLElement,
152
- callback: (event: MouseEvent | TouchEvent) => void,
153
- options: OnLongPressOptions = {},
154
- ): (() => void) => {
155
- const { duration = 500, threshold = 10 } = options;
156
-
157
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
158
- let startX = 0;
159
- let startY = 0;
160
-
161
- const getPoint = (e: MouseEvent | TouchEvent) => ('touches' in e ? e.touches[0] : e);
162
-
163
- const handleStart = markAsEventFunc((e: MouseEvent | TouchEvent) => {
164
- const point = getPoint(e);
165
- startX = point.clientX;
166
- startY = point.clientY;
167
-
168
- timeoutId = setTimeout(() => {
169
- callback(e);
170
- }, duration);
171
- });
172
-
173
- const handleMove = markAsEventFunc((e: MouseEvent | TouchEvent) => {
174
- if (!timeoutId) return;
175
-
176
- const point = getPoint(e);
177
- const deltaX = Math.abs(point.clientX - startX);
178
- const deltaY = Math.abs(point.clientY - startY);
179
-
180
- if (deltaX > threshold || deltaY > threshold) {
181
- handleEnd();
182
- }
183
- });
184
-
185
- const handleEnd = markAsEventFunc(() => {
186
- if (timeoutId) {
187
- clearTimeout(timeoutId);
188
- timeoutId = null;
189
- }
190
- });
191
-
192
- const removeListeners = addEventsListener(element, {
193
- mousedown: handleStart as EventListener,
194
- mousemove: handleMove as EventListener,
195
- mouseup: handleEnd,
196
- mouseleave: handleEnd,
197
- touchstart: handleStart as EventListener,
198
- touchmove: handleMove as EventListener,
199
- touchend: handleEnd,
200
- touchcancel: handleEnd,
201
- });
202
-
203
- return () => {
204
- removeListeners();
205
- if (timeoutId) clearTimeout(timeoutId);
206
- };
207
- };
208
-
209
- /**
210
- * Attaches multiple event listeners to an element.
211
- * @param element - The element to attach listeners to.
212
- * @param events - An object mapping event types to handlers.
213
- */
214
- export const addEventsListener = <E extends HTMLElement>(
215
- element: E,
216
- events: Partial<{
217
- [K in Parameters<E['addEventListener']>[0]]: SingleOrArray<
218
- Parameters<E['addEventListener']>[1]
219
- >;
220
- }>,
221
- options?: AddEventListenerOptions,
222
- ): (() => void) => {
223
- const handlers: [eventName: string, eventHandlers: EventListener][] = [];
224
-
225
- Object.entries(events).forEach(([eventKey, handler]) => {
226
- if (!handler) return;
227
- const event = eventKey;
228
-
229
- if (Array.isArray(handler)) {
230
- handler.forEach((h: EventListener) => {
231
- element.addEventListener(event, h, options);
232
- handlers.push([event, h]);
233
- });
234
- } else {
235
- element.addEventListener(event, handler as EventListener, options);
236
- handlers.push([event, handler as EventListener]);
237
- }
238
- });
239
-
240
- return () => {
241
- handlers.forEach(([event, handler]) => {
242
- element.removeEventListener(event, handler, options);
243
- });
244
- };
245
- };
package/src/dom/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './element';
2
- export * from './events';
3
- export * from './observer';
4
- export * from './style';
5
- export * from './win';
@@ -1,197 +0,0 @@
1
- /**
2
- * Options for waitForElement function.
3
- */
4
- export interface WaitForElementOptions {
5
- /** Maximum time to wait in milliseconds. Default: 5000 */
6
- timeout?: number;
7
- /** Root element to observe. Default: document.body */
8
- root?: HTMLElement;
9
- /** Check interval in milliseconds. Default: 100 */
10
- interval?: number;
11
- }
12
-
13
- /**
14
- * Waits for an element matching the selector to appear in the DOM.
15
- * @param selector - CSS selector to match.
16
- * @param options - Configuration options.
17
- * @returns A promise that resolves with the element when found.
18
- */
19
- export const waitForElement = <T extends HTMLElement = HTMLElement>(
20
- selector: string,
21
- options: WaitForElementOptions = {},
22
- ): Promise<T> => {
23
- const { timeout = 5000, root = document.body, interval = 100 } = options;
24
-
25
- return new Promise((resolve, reject) => {
26
- // Check if element already exists
27
- const existingElement = root.querySelector<T>(selector);
28
- if (existingElement) {
29
- resolve(existingElement);
30
- return;
31
- }
32
-
33
- // Use MutationObserver for efficient detection
34
- const observer = new MutationObserver(() => {
35
- const element = root.querySelector<T>(selector);
36
- if (element) {
37
- cleanup();
38
- resolve(element);
39
- }
40
- });
41
-
42
- observer.observe(root, {
43
- childList: true,
44
- subtree: true,
45
- });
46
-
47
- // Fallback interval check
48
- const intervalId = setInterval(() => {
49
- const element = root.querySelector<T>(selector);
50
- if (element) {
51
- cleanup();
52
- resolve(element);
53
- }
54
- }, interval);
55
-
56
- // Timeout handler
57
- const timeoutId = setTimeout(() => {
58
- cleanup();
59
- reject(new Error(`Timeout: Element "${selector}" not found within ${timeout}ms`));
60
- }, timeout);
61
-
62
- const cleanup = () => {
63
- observer.disconnect();
64
- clearTimeout(timeoutId);
65
- clearInterval(intervalId);
66
- };
67
- });
68
- };
69
-
70
- /**
71
- * Options for watchElementRemoval function.
72
- */
73
- export interface WatchElementRemovalOptions {
74
- /** Callback to invoke when the element is removed */
75
- onRemove?: () => void;
76
- /** Root element to observe. Default: document.body */
77
- root?: HTMLElement;
78
- }
79
-
80
- /**
81
- * Watches for an element to be removed from the DOM.
82
- * @param element - The element to watch.
83
- * @param options - Configuration options.
84
- * @returns A function to stop watching.
85
- */
86
- export const watchElementRemoval = (
87
- element: HTMLElement,
88
- options: WatchElementRemovalOptions = {},
89
- ): (() => void) => {
90
- const { onRemove, root = document.body } = options;
91
-
92
- const observer = new MutationObserver(() => {
93
- if (!root.contains(element)) {
94
- onRemove?.();
95
- observer.disconnect();
96
- }
97
- });
98
-
99
- observer.observe(root, {
100
- childList: true,
101
- subtree: true,
102
- });
103
-
104
- return () => observer.disconnect();
105
- };
106
-
107
- /**
108
- * Returns a promise that resolves when the element is removed from the DOM.
109
- * @param element - The element to watch.
110
- * @param options - Configuration options.
111
- * @returns A promise that resolves when the element is removed.
112
- */
113
- export const waitForElementRemoval = (
114
- element: HTMLElement,
115
- options: { root?: HTMLElement; timeout?: number } = {},
116
- ): Promise<void> => {
117
- const { root = document.body, timeout } = options;
118
-
119
- return new Promise((resolve, reject) => {
120
- // Check if element is already removed
121
- if (!root.contains(element)) {
122
- resolve();
123
- return;
124
- }
125
-
126
- let timeoutId: ReturnType<typeof setTimeout> | undefined;
127
-
128
- const observer = new MutationObserver(() => {
129
- if (!root.contains(element)) {
130
- cleanup();
131
- resolve();
132
- }
133
- });
134
-
135
- observer.observe(root, {
136
- childList: true,
137
- subtree: true,
138
- });
139
-
140
- if (timeout) {
141
- timeoutId = setTimeout(() => {
142
- cleanup();
143
- reject(new Error(`Timeout: Element not removed within ${timeout}ms`));
144
- }, timeout);
145
- }
146
-
147
- const cleanup = () => {
148
- observer.disconnect();
149
- if (timeoutId) clearTimeout(timeoutId);
150
- };
151
- });
152
- };
153
-
154
- /**
155
- * Options for watchElementChanges function.
156
- */
157
- export interface WatchElementChangesOptions {
158
- /** Watch for attribute changes. Default: true */
159
- attributes?: boolean;
160
- /** Watch for child node changes. Default: true */
161
- childList?: boolean;
162
- /** Watch for text content changes. Default: false */
163
- characterData?: boolean;
164
- /** Watch subtree. Default: false */
165
- subtree?: boolean;
166
- /** Callback for mutations */
167
- onMutation?: (mutations: MutationRecord[]) => void;
168
- }
169
-
170
- /**
171
- * Watches for changes to an element and its descendants.
172
- * @param element - The element to watch.
173
- * @param options - Configuration options.
174
- * @returns A function to stop watching.
175
- */
176
- export const watchElementChanges = (
177
- element: HTMLElement,
178
- options: WatchElementChangesOptions = {},
179
- ): (() => void) => {
180
- const {
181
- attributes = true,
182
- childList = true,
183
- characterData = false,
184
- subtree = false,
185
- onMutation,
186
- } = options;
187
-
188
- const observer = new MutationObserver((mutations) => onMutation?.(mutations));
189
- observer.observe(element, {
190
- attributes,
191
- childList,
192
- characterData,
193
- subtree,
194
- });
195
-
196
- return () => observer.disconnect();
197
- };
package/src/dom/style.ts DELETED
@@ -1,23 +0,0 @@
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
- };
@@ -1,19 +0,0 @@
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 DELETED
@@ -1,21 +0,0 @@
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 DELETED
@@ -1,4 +0,0 @@
1
- export const VERSION = '0.0.1';
2
-
3
- export * from './dom';
4
- export * from './utils';
package/src/main.ts DELETED
File without changes
@@ -1,4 +0,0 @@
1
- export * from './string';
2
- export * from './tag';
3
- export * from './timing';
4
- export * from './types';
@@ -1,17 +0,0 @@
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 DELETED
@@ -1,31 +0,0 @@
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];