@splendidlabz/utils 1.9.1 → 1.10.1
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/CHANGELOG.md +13 -0
- package/dist/cjs/dom/actions/index.cjs +75 -73
- package/dist/cjs/dom/actions/masonry.cjs +4 -2
- package/dist/cjs/dom/actions/prefer-horizontal-scroll.cjs +24 -24
- package/dist/cjs/dom/actions/sticky.cjs +4 -2
- package/dist/cjs/dom/font-size.cjs +9 -3
- package/dist/cjs/dom/index.cjs +81 -66
- package/dist/cjs/dom/observers/index.cjs +24 -17
- package/dist/cjs/dom/observers/resize-observer.cjs +4 -2
- package/dist/cjs/dom/observers/scroll-observer.cjs +24 -17
- package/dist/esm/dom/actions/index.js +75 -73
- package/dist/esm/dom/actions/masonry.js +4 -2
- package/dist/esm/dom/actions/prefer-horizontal-scroll.js +24 -24
- package/dist/esm/dom/actions/sticky.js +4 -2
- package/dist/esm/dom/font-size.js +8 -3
- package/dist/esm/dom/index.js +80 -66
- package/dist/esm/dom/observers/index.js +24 -17
- package/dist/esm/dom/observers/resize-observer.js +4 -2
- package/dist/esm/dom/observers/scroll-observer.js +24 -17
- package/dist/types/dom/events.d.cts +2 -2
- package/dist/types/dom/font-size.d.cts +3 -2
- package/dist/types/dom/index.d.cts +2 -2
- package/dist/types/dom/observers/index.d.cts +1 -1
- package/dist/types/dom/observers/resize-observer.d.cts +3 -3
- package/dist/types/dom/observers/scroll-observer.d.cts +34 -21
- package/dist/types/dom/spam-prevention.d.cts +7 -7
- package/dist/types/lib/checks.d.cts +1 -1
- package/package.json +1 -1
- package/src/dom/actions/prefer-horizontal-scroll.js +3 -8
- package/src/dom/events.js +1 -1
- package/src/dom/font-size.js +9 -4
- package/src/dom/observers/resize-observer.js +6 -4
- package/src/dom/observers/scroll-observer.js +41 -27
- package/src/dom/spam-prevention.js +5 -4
- package/src/dom/events.test.js +0 -99
|
@@ -102,12 +102,14 @@ function mutationObserver(target, options) {
|
|
|
102
102
|
function resizeObserver(target, options) {
|
|
103
103
|
const { callback, ...opts } = options;
|
|
104
104
|
const observer = new ResizeObserver(observerFn);
|
|
105
|
-
if (target === window
|
|
105
|
+
if (target === window || target === document)
|
|
106
|
+
target = document.documentElement;
|
|
106
107
|
useObserverMethodOnTarget(target, observer, "observe", opts);
|
|
107
108
|
function observerFn(entries) {
|
|
108
109
|
for (const entry of entries) {
|
|
109
110
|
if (callback) callback({ entry, entries, observer });
|
|
110
|
-
else
|
|
111
|
+
else
|
|
112
|
+
dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
return {
|
|
@@ -128,40 +130,44 @@ var defaultOptions2 = {
|
|
|
128
130
|
// Float between 0 to 1.
|
|
129
131
|
tolerance: 0.1,
|
|
130
132
|
// Float between 0 to 1. Tolerance for event firing
|
|
131
|
-
throttle: 16,
|
|
132
|
-
// Throttle interval in ms (default: ~60fps)
|
|
133
133
|
once: false
|
|
134
134
|
// Only fire threshold callback once
|
|
135
135
|
};
|
|
136
|
+
function getScrollElement(node) {
|
|
137
|
+
if (node === document || node === window || node === document.documentElement) {
|
|
138
|
+
return document.documentElement;
|
|
139
|
+
}
|
|
140
|
+
return (
|
|
141
|
+
/** @type {Element} */
|
|
142
|
+
node
|
|
143
|
+
);
|
|
144
|
+
}
|
|
136
145
|
function scrollObserver(node, options = {}) {
|
|
137
146
|
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
|
|
138
147
|
const opts = { ...defaultOptions2, ...userOpts };
|
|
139
|
-
const { threshold, tolerance,
|
|
140
|
-
|
|
148
|
+
const { threshold, tolerance, once } = opts;
|
|
149
|
+
let prevScrollDirection = null;
|
|
141
150
|
let prevScrollTop = 0;
|
|
142
151
|
let prevScrollPercent = 0;
|
|
143
|
-
let lastThrottleTime = 0;
|
|
144
152
|
let thresholdFired = false;
|
|
145
153
|
let rafId = null;
|
|
146
|
-
const isDocumentScroll = node === document || node === window;
|
|
147
|
-
const scrollElement =
|
|
154
|
+
const isDocumentScroll = node === document || node === window || node === document.documentElement;
|
|
155
|
+
const scrollElement = getScrollElement(node);
|
|
156
|
+
const eventTarget = isDocumentScroll ? window : node;
|
|
148
157
|
let cachedScrollHeight = 0;
|
|
149
158
|
let cachedClientHeight = 0;
|
|
150
159
|
updateCache();
|
|
151
160
|
const cacheObserver = resizeObserver(scrollElement, {
|
|
152
161
|
callback: updateCache
|
|
153
162
|
});
|
|
154
|
-
|
|
163
|
+
eventTarget.addEventListener("scroll", throttledObserve, { passive: true });
|
|
164
|
+
function throttledObserve() {
|
|
165
|
+
rafId = requestAnimationFrame(observe);
|
|
166
|
+
}
|
|
155
167
|
function updateCache() {
|
|
156
168
|
cachedScrollHeight = scrollElement.scrollHeight;
|
|
157
169
|
cachedClientHeight = scrollElement.clientHeight;
|
|
158
170
|
}
|
|
159
|
-
function throttledObserve() {
|
|
160
|
-
const now = Date.now();
|
|
161
|
-
if (now - lastThrottleTime < throttle) return;
|
|
162
|
-
lastThrottleTime = now;
|
|
163
|
-
rafId = requestAnimationFrame(observe);
|
|
164
|
-
}
|
|
165
171
|
function observe() {
|
|
166
172
|
const scrollTop = scrollElement.scrollTop;
|
|
167
173
|
if (Math.abs(scrollTop - prevScrollTop) < 1) return;
|
|
@@ -200,10 +206,11 @@ function scrollObserver(node, options = {}) {
|
|
|
200
206
|
}
|
|
201
207
|
prevScrollTop = scrollTop;
|
|
202
208
|
prevScrollPercent = scrollPercent;
|
|
209
|
+
prevScrollDirection = scrollDirection;
|
|
203
210
|
}
|
|
204
211
|
return {
|
|
205
212
|
destroy() {
|
|
206
|
-
|
|
213
|
+
eventTarget.removeEventListener("scroll", throttledObserve);
|
|
207
214
|
if (rafId) cancelAnimationFrame(rafId);
|
|
208
215
|
cacheObserver.destroy();
|
|
209
216
|
}
|
|
@@ -32,12 +32,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
|
|
|
32
32
|
function resizeObserver(target, options) {
|
|
33
33
|
const { callback, ...opts } = options;
|
|
34
34
|
const observer = new ResizeObserver(observerFn);
|
|
35
|
-
if (target === window
|
|
35
|
+
if (target === window || target === document)
|
|
36
|
+
target = document.documentElement;
|
|
36
37
|
useObserverMethodOnTarget(target, observer, "observe", opts);
|
|
37
38
|
function observerFn(entries) {
|
|
38
39
|
for (const entry of entries) {
|
|
39
40
|
if (callback) callback({ entry, entries, observer });
|
|
40
|
-
else
|
|
41
|
+
else
|
|
42
|
+
dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
return {
|
|
@@ -32,12 +32,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
|
|
|
32
32
|
function resizeObserver(target, options) {
|
|
33
33
|
const { callback, ...opts } = options;
|
|
34
34
|
const observer = new ResizeObserver(observerFn);
|
|
35
|
-
if (target === window
|
|
35
|
+
if (target === window || target === document)
|
|
36
|
+
target = document.documentElement;
|
|
36
37
|
useObserverMethodOnTarget(target, observer, "observe", opts);
|
|
37
38
|
function observerFn(entries) {
|
|
38
39
|
for (const entry of entries) {
|
|
39
40
|
if (callback) callback({ entry, entries, observer });
|
|
40
|
-
else
|
|
41
|
+
else
|
|
42
|
+
dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
return {
|
|
@@ -58,40 +60,44 @@ var defaultOptions = {
|
|
|
58
60
|
// Float between 0 to 1.
|
|
59
61
|
tolerance: 0.1,
|
|
60
62
|
// Float between 0 to 1. Tolerance for event firing
|
|
61
|
-
throttle: 16,
|
|
62
|
-
// Throttle interval in ms (default: ~60fps)
|
|
63
63
|
once: false
|
|
64
64
|
// Only fire threshold callback once
|
|
65
65
|
};
|
|
66
|
+
function getScrollElement(node) {
|
|
67
|
+
if (node === document || node === window || node === document.documentElement) {
|
|
68
|
+
return document.documentElement;
|
|
69
|
+
}
|
|
70
|
+
return (
|
|
71
|
+
/** @type {Element} */
|
|
72
|
+
node
|
|
73
|
+
);
|
|
74
|
+
}
|
|
66
75
|
function scrollObserver(node, options = {}) {
|
|
67
76
|
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
|
|
68
77
|
const opts = { ...defaultOptions, ...userOpts };
|
|
69
|
-
const { threshold, tolerance,
|
|
70
|
-
|
|
78
|
+
const { threshold, tolerance, once } = opts;
|
|
79
|
+
let prevScrollDirection = null;
|
|
71
80
|
let prevScrollTop = 0;
|
|
72
81
|
let prevScrollPercent = 0;
|
|
73
|
-
let lastThrottleTime = 0;
|
|
74
82
|
let thresholdFired = false;
|
|
75
83
|
let rafId = null;
|
|
76
|
-
const isDocumentScroll = node === document || node === window;
|
|
77
|
-
const scrollElement =
|
|
84
|
+
const isDocumentScroll = node === document || node === window || node === document.documentElement;
|
|
85
|
+
const scrollElement = getScrollElement(node);
|
|
86
|
+
const eventTarget = isDocumentScroll ? window : node;
|
|
78
87
|
let cachedScrollHeight = 0;
|
|
79
88
|
let cachedClientHeight = 0;
|
|
80
89
|
updateCache();
|
|
81
90
|
const cacheObserver = resizeObserver(scrollElement, {
|
|
82
91
|
callback: updateCache
|
|
83
92
|
});
|
|
84
|
-
|
|
93
|
+
eventTarget.addEventListener("scroll", throttledObserve, { passive: true });
|
|
94
|
+
function throttledObserve() {
|
|
95
|
+
rafId = requestAnimationFrame(observe);
|
|
96
|
+
}
|
|
85
97
|
function updateCache() {
|
|
86
98
|
cachedScrollHeight = scrollElement.scrollHeight;
|
|
87
99
|
cachedClientHeight = scrollElement.clientHeight;
|
|
88
100
|
}
|
|
89
|
-
function throttledObserve() {
|
|
90
|
-
const now = Date.now();
|
|
91
|
-
if (now - lastThrottleTime < throttle) return;
|
|
92
|
-
lastThrottleTime = now;
|
|
93
|
-
rafId = requestAnimationFrame(observe);
|
|
94
|
-
}
|
|
95
101
|
function observe() {
|
|
96
102
|
const scrollTop = scrollElement.scrollTop;
|
|
97
103
|
if (Math.abs(scrollTop - prevScrollTop) < 1) return;
|
|
@@ -130,10 +136,11 @@ function scrollObserver(node, options = {}) {
|
|
|
130
136
|
}
|
|
131
137
|
prevScrollTop = scrollTop;
|
|
132
138
|
prevScrollPercent = scrollPercent;
|
|
139
|
+
prevScrollDirection = scrollDirection;
|
|
133
140
|
}
|
|
134
141
|
return {
|
|
135
142
|
destroy() {
|
|
136
|
-
|
|
143
|
+
eventTarget.removeEventListener("scroll", throttledObserve);
|
|
137
144
|
if (rafId) cancelAnimationFrame(rafId);
|
|
138
145
|
cacheObserver.destroy();
|
|
139
146
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @typedef {Object} EventListener
|
|
3
3
|
* @property {Element} node - The DOM element to attach the event listener to
|
|
4
4
|
* @property {string} event - The event type (e.g., 'click', 'keydown')
|
|
5
|
-
* @property {
|
|
5
|
+
* @property {EventListenerOrEventListenerObject} handler - The event handler function
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {Object} ListenerManager
|
|
@@ -54,7 +54,7 @@ type EventListener = {
|
|
|
54
54
|
/**
|
|
55
55
|
* - The event handler function
|
|
56
56
|
*/
|
|
57
|
-
handler:
|
|
57
|
+
handler: EventListenerOrEventListenerObject;
|
|
58
58
|
};
|
|
59
59
|
type ListenerManager = {
|
|
60
60
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
declare function getUnit(value: any): any;
|
|
2
|
-
declare function em(element?: HTMLElement, multiple?: number): number;
|
|
3
2
|
declare function rem(multiple?: number): number;
|
|
3
|
+
declare function em(element?: HTMLElement, multiple?: number): number;
|
|
4
|
+
declare function rlh(multiple?: number): number;
|
|
4
5
|
declare function lh(element?: HTMLElement, multiple?: number): number;
|
|
5
6
|
declare function toPx(value: any, element?: any): number;
|
|
6
7
|
|
|
7
|
-
export { em, getUnit, lh, rem, toPx };
|
|
8
|
+
export { em, getUnit, lh, rem, rlh, toPx };
|
|
@@ -8,7 +8,7 @@ export { cookies, getCookie } from './cookie.cjs';
|
|
|
8
8
|
export { getCSSValue, getCSSVar, setCSSValue, setCSSVar } from './css-vars.cjs';
|
|
9
9
|
export { CustomEventOptions, EventListener, ListenerManager, addListeners, dispatchEvent, removeListeners, updateEvent } from './events.cjs';
|
|
10
10
|
export { Focusable, Focusables, getFocusableElements } from './focusable.cjs';
|
|
11
|
-
export { em, getUnit, lh, rem, toPx } from './font-size.cjs';
|
|
11
|
+
export { em, getUnit, lh, rem, rlh, toPx } from './font-size.cjs';
|
|
12
12
|
export { getAncestorWithSiblings, getChildrenElements, getElement, getNodeType, getParentElement, getSelfIndex, getSiblingElements, isAncestor } from './get-element.cjs';
|
|
13
13
|
export { sha256Hash } from './hash.cjs';
|
|
14
14
|
export { isArrowKey, isEscapeKey, isShiftTab, isTab, isTabKey } from './keyboard.cjs';
|
|
@@ -17,7 +17,7 @@ export { imagesLoaded, mediaLoaded, videosLoaded } from './media.cjs';
|
|
|
17
17
|
export { intersectionObserver } from './observers/intersection-observer.cjs';
|
|
18
18
|
export { mutationObserver } from './observers/mutation-observer.cjs';
|
|
19
19
|
export { resizeObserver } from './observers/resize-observer.cjs';
|
|
20
|
-
export { scrollObserver } from './observers/scroll-observer.cjs';
|
|
20
|
+
export { ScrollObserverOptions, scrollObserver } from './observers/scroll-observer.cjs';
|
|
21
21
|
export { PKCE } from './pkce.cjs';
|
|
22
22
|
export { queryParams } from './query-params.cjs';
|
|
23
23
|
export { randomString, uuid } from './random-string.cjs';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { intersectionObserver } from './intersection-observer.cjs';
|
|
2
2
|
export { mutationObserver } from './mutation-observer.cjs';
|
|
3
3
|
export { resizeObserver } from './resize-observer.cjs';
|
|
4
|
-
export { scrollObserver } from './scroll-observer.cjs';
|
|
4
|
+
export { ScrollObserverOptions, scrollObserver } from './scroll-observer.cjs';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates and manages a ResizeObserver instance to monitor size changes of a target element.
|
|
3
3
|
*
|
|
4
|
-
* @param {Element|Window|NodeList|Element[]} target - The element(s) to observe.
|
|
5
|
-
* - If window is provided, document.
|
|
4
|
+
* @param {Element|Window|Document|NodeList|Element[]} target - The element(s) to observe.
|
|
5
|
+
* - If window or document is provided, document.documentElement will be observed instead.
|
|
6
6
|
* - If NodeList or Array of elements is provided, all elements will be observed.
|
|
7
7
|
* @param {Object} options - Configuration options for the resize observer
|
|
8
8
|
* @param {boolean} [options.observe=true] - Whether to start observing immediately. If false, the observer won't be created.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* console.log('Element resized:', detail.entry.contentRect);
|
|
32
32
|
* });
|
|
33
33
|
*/
|
|
34
|
-
declare function resizeObserver(target: Element | Window | NodeList | Element[], options: {
|
|
34
|
+
declare function resizeObserver(target: Element | Window | Document | NodeList | Element[], options: {
|
|
35
35
|
observe?: boolean;
|
|
36
36
|
callback?: Function;
|
|
37
37
|
observerOptions?: any;
|
|
@@ -1,27 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scroll Observer - Optimized for performance
|
|
3
|
-
* @param {Element} node - The element to observe scroll events on
|
|
4
|
-
* @param {
|
|
5
|
-
* @param {Number} options.threshold - Float between 0 to 1. When to trigger callback (0.75 = 75% down page)
|
|
6
|
-
* @param {Number} options.tolerance - Float between 0 to 1. Tolerance zone around threshold
|
|
7
|
-
* @param {Number} options.throttle - Throttle interval in ms (default: 16ms for ~60fps)
|
|
8
|
-
* @param {Boolean} options.once - Only fire threshold callback once (default: false)
|
|
9
|
-
* @param {Function} options.callback - Called on every scroll with scrollPercent
|
|
10
|
-
* @param {Function} options.onScrollDown - Called when scrolling down
|
|
11
|
-
* @param {Function} options.onScrollUp - Called when scrolling up
|
|
12
|
-
* @param {Function} options.onEnterThreshold - Called when entering threshold zone
|
|
3
|
+
* @param {Element | Document | Window} node - The element to observe scroll events on
|
|
4
|
+
* @param {ScrollObserverOptions} [options] - Configuration options
|
|
13
5
|
*/
|
|
14
|
-
declare function scrollObserver(node: Element, options?: {
|
|
15
|
-
threshold: number;
|
|
16
|
-
tolerance: number;
|
|
17
|
-
throttle: number;
|
|
18
|
-
once: boolean;
|
|
19
|
-
callback: Function;
|
|
20
|
-
onScrollDown: Function;
|
|
21
|
-
onScrollUp: Function;
|
|
22
|
-
onEnterThreshold: Function;
|
|
23
|
-
}): {
|
|
6
|
+
declare function scrollObserver(node: Element | Document | Window, options?: ScrollObserverOptions): {
|
|
24
7
|
destroy(): void;
|
|
25
8
|
};
|
|
9
|
+
type ScrollObserverOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* - Float between 0 to 1. When to trigger callback (0.75 = 75% down page)
|
|
12
|
+
*/
|
|
13
|
+
threshold?: number;
|
|
14
|
+
/**
|
|
15
|
+
* - Float between 0 to 1. Tolerance zone around threshold
|
|
16
|
+
*/
|
|
17
|
+
tolerance?: number;
|
|
18
|
+
/**
|
|
19
|
+
* - Only fire threshold callback once (default: false)
|
|
20
|
+
*/
|
|
21
|
+
once?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* - Called on every scroll with scrollPercent
|
|
24
|
+
*/
|
|
25
|
+
callback?: Function;
|
|
26
|
+
/**
|
|
27
|
+
* - Called when scrolling down
|
|
28
|
+
*/
|
|
29
|
+
onScrollDown?: Function;
|
|
30
|
+
/**
|
|
31
|
+
* - Called when scrolling up
|
|
32
|
+
*/
|
|
33
|
+
onScrollUp?: Function;
|
|
34
|
+
/**
|
|
35
|
+
* - Called when entering threshold zone
|
|
36
|
+
*/
|
|
37
|
+
onEnterThreshold?: Function;
|
|
38
|
+
};
|
|
26
39
|
|
|
27
|
-
export { scrollObserver };
|
|
40
|
+
export { type ScrollObserverOptions, scrollObserver };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Enhances a form element with spam detection capabilities
|
|
3
3
|
* @param {HTMLFormElement} form - The form element to enhance
|
|
4
|
-
* @param {Object} options - Configuration options
|
|
5
|
-
* @param {string} options.honeypotField - Name of the honeypot field (default: 'hp')
|
|
6
|
-
* @param {number} options.honeypotDuration - Minimum time in ms for legitimate form fill (default: 2000)
|
|
7
|
-
* @returns {
|
|
4
|
+
* @param {Object} [options] - Configuration options
|
|
5
|
+
* @param {string} [options.honeypotField] - Name of the honeypot field (default: 'hp')
|
|
6
|
+
* @param {number} [options.honeypotDuration] - Minimum time in ms for legitimate form fill (default: 2000)
|
|
7
|
+
* @returns {void}
|
|
8
8
|
*/
|
|
9
9
|
declare function preventSpam(form: HTMLFormElement, { honeypotField, honeypotDuration }?: {
|
|
10
|
-
honeypotField
|
|
11
|
-
honeypotDuration
|
|
12
|
-
}):
|
|
10
|
+
honeypotField?: string;
|
|
11
|
+
honeypotDuration?: number;
|
|
12
|
+
}): void;
|
|
13
13
|
|
|
14
14
|
export { preventSpam };
|
|
@@ -27,6 +27,6 @@ declare function isArray(x: unknown): boolean;
|
|
|
27
27
|
* notObject({}) // false
|
|
28
28
|
*/
|
|
29
29
|
declare function notObject(x: unknown): boolean;
|
|
30
|
-
declare function getType(x: any): "
|
|
30
|
+
declare function getType(x: any): "string" | "integer" | "float" | "boolean" | "function" | "undefined" | "symbol" | "bigint" | "array" | "object" | "unknown";
|
|
31
31
|
|
|
32
32
|
export { getType, isArray, isObject, notObject };
|
package/package.json
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
removeListeners,
|
|
5
|
-
setCSSVar,
|
|
6
|
-
} from '../index.js'
|
|
7
|
-
|
|
8
|
-
import { omitEmpty } from '../../lib/index.js'
|
|
1
|
+
import { omitEmpty } from '../../lib/objects/omit-empty.js'
|
|
2
|
+
import { getCSSVar, setCSSVar } from '../css-vars.js'
|
|
3
|
+
import { addListeners, removeListeners } from '../events.js'
|
|
9
4
|
|
|
10
5
|
const DEFAULT_OPTIONS = {
|
|
11
6
|
scrollSnapDelay: 1000,
|
package/src/dom/events.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @typedef {Object} EventListener
|
|
3
3
|
* @property {Element} node - The DOM element to attach the event listener to
|
|
4
4
|
* @property {string} event - The event type (e.g., 'click', 'keydown')
|
|
5
|
-
* @property {
|
|
5
|
+
* @property {EventListenerOrEventListenerObject} handler - The event handler function
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
package/src/dom/font-size.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { splitUnit } from '../lib/index.js'
|
|
1
|
+
import { splitUnit } from '../lib/numbers/index.js'
|
|
2
2
|
|
|
3
3
|
export function getUnit(value) {
|
|
4
4
|
const [, unit] = splitUnit(value)
|
|
5
5
|
return unit
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export function rem(multiple = 1) {
|
|
9
|
+
const value = parseFloat(getComputedStyle(document.body)['font-size'])
|
|
10
|
+
return Math.round(value * multiple)
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export function em(element = document.body, multiple = 1) {
|
|
9
14
|
const value = parseFloat(getComputedStyle(element)['font-size'])
|
|
10
15
|
return Math.round(value * multiple)
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
export function
|
|
14
|
-
const
|
|
15
|
-
return Math.round(
|
|
18
|
+
export function rlh(multiple = 1) {
|
|
19
|
+
const lineHeight = parseFloat(getComputedStyle(document.body)['line-height'])
|
|
20
|
+
return Math.round(lineHeight * multiple)
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export function lh(element = document.body, multiple = 1) {
|
|
@@ -5,8 +5,8 @@ import { useObserverMethodOnTarget } from './observer.js'
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates and manages a ResizeObserver instance to monitor size changes of a target element.
|
|
7
7
|
*
|
|
8
|
-
* @param {Element|Window|NodeList|Element[]} target - The element(s) to observe.
|
|
9
|
-
* - If window is provided, document.
|
|
8
|
+
* @param {Element|Window|Document|NodeList|Element[]} target - The element(s) to observe.
|
|
9
|
+
* - If window or document is provided, document.documentElement will be observed instead.
|
|
10
10
|
* - If NodeList or Array of elements is provided, all elements will be observed.
|
|
11
11
|
* @param {Object} options - Configuration options for the resize observer
|
|
12
12
|
* @param {boolean} [options.observe=true] - Whether to start observing immediately. If false, the observer won't be created.
|
|
@@ -39,13 +39,15 @@ export function resizeObserver(target, options) {
|
|
|
39
39
|
const { callback, ...opts } = options
|
|
40
40
|
const observer = new ResizeObserver(observerFn)
|
|
41
41
|
|
|
42
|
-
if (target === window
|
|
42
|
+
if (target === window || target === document)
|
|
43
|
+
target = document.documentElement
|
|
43
44
|
useObserverMethodOnTarget(target, observer, 'observe', opts)
|
|
44
45
|
|
|
45
46
|
function observerFn(entries) {
|
|
46
47
|
for (const entry of entries) {
|
|
47
48
|
if (callback) callback({ entry, entries, observer })
|
|
48
|
-
else
|
|
49
|
+
else
|
|
50
|
+
dispatchEvent(entry.target, 'resize-obs', { entry, entries, observer })
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -4,40 +4,57 @@ import { resizeObserver } from './resize-observer.js'
|
|
|
4
4
|
const defaultOptions = {
|
|
5
5
|
threshold: 0, // Float between 0 to 1.
|
|
6
6
|
tolerance: 0.1, // Float between 0 to 1. Tolerance for event firing
|
|
7
|
-
throttle: 16, // Throttle interval in ms (default: ~60fps)
|
|
8
7
|
once: false, // Only fire threshold callback once
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} ScrollObserverOptions
|
|
12
|
+
* @property {number} [threshold] - Float between 0 to 1. When to trigger callback (0.75 = 75% down page)
|
|
13
|
+
* @property {number} [tolerance] - Float between 0 to 1. Tolerance zone around threshold
|
|
14
|
+
* @property {boolean} [once] - Only fire threshold callback once (default: false)
|
|
15
|
+
* @property {Function} [callback] - Called on every scroll with scrollPercent
|
|
16
|
+
* @property {Function} [onScrollDown] - Called when scrolling down
|
|
17
|
+
* @property {Function} [onScrollUp] - Called when scrolling up
|
|
18
|
+
* @property {Function} [onEnterThreshold] - Called when entering threshold zone
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Element | Document | Window} node
|
|
23
|
+
* @returns {Element}
|
|
24
|
+
*/
|
|
25
|
+
function getScrollElement(node) {
|
|
26
|
+
if (
|
|
27
|
+
node === document ||
|
|
28
|
+
node === window ||
|
|
29
|
+
node === document.documentElement
|
|
30
|
+
) {
|
|
31
|
+
return document.documentElement
|
|
32
|
+
}
|
|
33
|
+
return /** @type {Element} */ (node)
|
|
34
|
+
}
|
|
35
|
+
|
|
12
36
|
/**
|
|
13
37
|
* Scroll Observer - Optimized for performance
|
|
14
|
-
* @param {Element} node - The element to observe scroll events on
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {Number} options.threshold - Float between 0 to 1. When to trigger callback (0.75 = 75% down page)
|
|
17
|
-
* @param {Number} options.tolerance - Float between 0 to 1. Tolerance zone around threshold
|
|
18
|
-
* @param {Number} options.throttle - Throttle interval in ms (default: 16ms for ~60fps)
|
|
19
|
-
* @param {Boolean} options.once - Only fire threshold callback once (default: false)
|
|
20
|
-
* @param {Function} options.callback - Called on every scroll with scrollPercent
|
|
21
|
-
* @param {Function} options.onScrollDown - Called when scrolling down
|
|
22
|
-
* @param {Function} options.onScrollUp - Called when scrolling up
|
|
23
|
-
* @param {Function} options.onEnterThreshold - Called when entering threshold zone
|
|
38
|
+
* @param {Element | Document | Window} node - The element to observe scroll events on
|
|
39
|
+
* @param {ScrollObserverOptions} [options] - Configuration options
|
|
24
40
|
*/
|
|
25
41
|
export function scrollObserver(node, options = {}) {
|
|
26
42
|
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } =
|
|
27
43
|
options
|
|
28
44
|
const opts = { ...defaultOptions, ...userOpts }
|
|
29
|
-
const { threshold, tolerance,
|
|
45
|
+
const { threshold, tolerance, once } = opts
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
let prevScrollDirection = null
|
|
32
48
|
let prevScrollTop = 0
|
|
33
49
|
let prevScrollPercent = 0
|
|
34
|
-
let lastThrottleTime = 0
|
|
35
50
|
let thresholdFired = false
|
|
36
51
|
let rafId = null
|
|
37
52
|
|
|
38
53
|
// Determine scroll context once at initialization
|
|
39
|
-
const isDocumentScroll =
|
|
40
|
-
|
|
54
|
+
const isDocumentScroll =
|
|
55
|
+
node === document || node === window || node === document.documentElement
|
|
56
|
+
const scrollElement = getScrollElement(node)
|
|
57
|
+
const eventTarget = isDocumentScroll ? window : node
|
|
41
58
|
|
|
42
59
|
// Cache DOM references and expensive calculations
|
|
43
60
|
let cachedScrollHeight = 0
|
|
@@ -49,21 +66,17 @@ export function scrollObserver(node, options = {}) {
|
|
|
49
66
|
callback: updateCache,
|
|
50
67
|
})
|
|
51
68
|
|
|
52
|
-
|
|
69
|
+
eventTarget.addEventListener('scroll', throttledObserve, { passive: true })
|
|
70
|
+
|
|
71
|
+
function throttledObserve() {
|
|
72
|
+
rafId = requestAnimationFrame(observe)
|
|
73
|
+
}
|
|
53
74
|
|
|
54
75
|
function updateCache() {
|
|
55
76
|
cachedScrollHeight = scrollElement.scrollHeight
|
|
56
77
|
cachedClientHeight = scrollElement.clientHeight
|
|
57
78
|
}
|
|
58
79
|
|
|
59
|
-
function throttledObserve() {
|
|
60
|
-
const now = Date.now()
|
|
61
|
-
if (now - lastThrottleTime < throttle) return
|
|
62
|
-
|
|
63
|
-
lastThrottleTime = now
|
|
64
|
-
rafId = requestAnimationFrame(observe)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
80
|
function observe() {
|
|
68
81
|
const scrollTop = scrollElement.scrollTop
|
|
69
82
|
|
|
@@ -120,11 +133,12 @@ export function scrollObserver(node, options = {}) {
|
|
|
120
133
|
|
|
121
134
|
prevScrollTop = scrollTop
|
|
122
135
|
prevScrollPercent = scrollPercent
|
|
136
|
+
prevScrollDirection = scrollDirection
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
return {
|
|
126
140
|
destroy() {
|
|
127
|
-
|
|
141
|
+
eventTarget.removeEventListener('scroll', throttledObserve)
|
|
128
142
|
if (rafId) cancelAnimationFrame(rafId)
|
|
129
143
|
cacheObserver.destroy()
|
|
130
144
|
},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Enhances a form element with spam detection capabilities
|
|
3
3
|
* @param {HTMLFormElement} form - The form element to enhance
|
|
4
|
-
* @param {Object} options - Configuration options
|
|
5
|
-
* @param {string} options.honeypotField - Name of the honeypot field (default: 'hp')
|
|
6
|
-
* @param {number} options.honeypotDuration - Minimum time in ms for legitimate form fill (default: 2000)
|
|
7
|
-
* @returns {
|
|
4
|
+
* @param {Object} [options] - Configuration options
|
|
5
|
+
* @param {string} [options.honeypotField] - Name of the honeypot field (default: 'hp')
|
|
6
|
+
* @param {number} [options.honeypotDuration] - Minimum time in ms for legitimate form fill (default: 2000)
|
|
7
|
+
* @returns {void}
|
|
8
8
|
*/
|
|
9
9
|
export function preventSpam(
|
|
10
10
|
form,
|
|
@@ -25,6 +25,7 @@ export function preventSpam(
|
|
|
25
25
|
form.containsSpam = function () {
|
|
26
26
|
const fillTime = Date.now() - startTime
|
|
27
27
|
const isTooFast = fillTime < honeypotDuration
|
|
28
|
+
/** @type {HTMLInputElement | null} */
|
|
28
29
|
const honeypotInput = form.querySelector(`[name="${honeypotField}"]`)
|
|
29
30
|
const hasHoneypotValue = honeypotInput?.value?.trim()
|
|
30
31
|
const noInteraction = !hasInteraction
|