@splendidlabz/utils 1.7.0 → 1.8.2

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/dom/font-size.cjs +1 -1
  3. package/dist/cjs/dom/index.cjs +91 -1
  4. package/dist/cjs/dom/observers/index.cjs +92 -2
  5. package/dist/cjs/dom/observers/scroll-observer.cjs +170 -0
  6. package/dist/cjs/lib/date/index.cjs +3 -1
  7. package/dist/cjs/lib/date/time.cjs +3 -1
  8. package/dist/cjs/lib/functions/functional.cjs +20 -0
  9. package/dist/cjs/lib/functions/index.cjs +20 -0
  10. package/dist/cjs/lib/index.cjs +27 -2
  11. package/dist/cjs/lib/numbers/index.cjs +1 -1
  12. package/dist/cjs/lib/objects/extend.cjs +2 -1
  13. package/dist/cjs/lib/objects/index.cjs +4 -1
  14. package/dist/cjs/lib/objects/mix/mix.cjs +4 -1
  15. package/dist/cjs/node/file-cache.cjs +3 -3
  16. package/dist/cjs/node/index.cjs +3 -3
  17. package/dist/esm/dom/font-size.js +1 -1
  18. package/dist/esm/dom/index.js +90 -1
  19. package/dist/esm/dom/observers/index.js +90 -1
  20. package/dist/esm/dom/observers/scroll-observer.js +144 -0
  21. package/dist/esm/lib/date/index.js +3 -1
  22. package/dist/esm/lib/date/time.js +3 -1
  23. package/dist/esm/lib/functions/functional.js +18 -0
  24. package/dist/esm/lib/functions/index.js +18 -0
  25. package/dist/esm/lib/index.js +24 -2
  26. package/dist/esm/lib/numbers/index.js +1 -1
  27. package/dist/esm/lib/objects/extend.js +2 -1
  28. package/dist/esm/lib/objects/index.js +3 -1
  29. package/dist/esm/lib/objects/mix/mix.js +3 -1
  30. package/dist/esm/node/file-cache.js +1 -1
  31. package/dist/esm/node/index.js +1 -1
  32. package/dist/types/dom/index.d.cts +1 -0
  33. package/dist/types/dom/observers/index.d.cts +1 -0
  34. package/dist/types/dom/observers/scroll-observer.d.cts +27 -0
  35. package/dist/types/lib/functions/functional.d.cts +71 -5
  36. package/dist/types/lib/functions/index.d.cts +1 -1
  37. package/dist/types/lib/index.d.cts +2 -2
  38. package/dist/types/lib/objects/extend.d.cts +1 -1
  39. package/dist/types/lib/objects/index.d.cts +1 -1
  40. package/dist/types/lib/objects/mix/mix.d.cts +43 -3
  41. package/package.json +2 -3
  42. package/src/dom/observers/index.js +1 -0
  43. package/src/dom/observers/resize-observer.js +5 -5
  44. package/src/dom/observers/scroll-observer.js +132 -0
  45. package/src/lib/date/time.js +2 -0
  46. package/src/lib/functions/functional.js +82 -0
  47. package/src/lib/functions/functional.test.js +196 -0
  48. package/src/lib/numbers/index.js +1 -1
  49. package/src/lib/numbers/split-unit.test.js +45 -0
  50. package/src/lib/objects/mix/mix.js +74 -4
  51. package/src/lib/objects/mix/mix.test.js +51 -2
  52. package/src/node/file-cache.js +1 -1
@@ -24,8 +24,9 @@ function curry(fn) {
24
24
  }
25
25
 
26
26
  // src/lib/objects/mix/mix.js
27
- var DEFAULT_OPTIONS = { array: "concat" };
27
+ var DEFAULT_OPTIONS = { array: "replace" };
28
28
  var mix = createMix();
29
+ var concatMix = createMix({ array: "concat" });
29
30
  function createMix(userOptions = {}) {
30
31
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
31
32
  function _mix(...sources) {
@@ -94,8 +94,9 @@ function pipe(...fns) {
94
94
  }
95
95
 
96
96
  // src/lib/objects/mix/mix.js
97
- var DEFAULT_OPTIONS = { array: "concat" };
97
+ var DEFAULT_OPTIONS = { array: "replace" };
98
98
  var mix = createMix();
99
+ var concatMix = createMix({ array: "concat" });
99
100
  function createMix(userOptions = {}) {
100
101
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
101
102
  function _mix(...sources) {
@@ -300,6 +301,7 @@ function splitObject(obj, keys) {
300
301
  }
301
302
  export {
302
303
  camelCaseKeys,
304
+ concatMix,
303
305
  createMix,
304
306
  extendObject,
305
307
  flattenObject,
@@ -1,6 +1,7 @@
1
1
  // src/lib/objects/mix/mix.js
2
- var DEFAULT_OPTIONS = { array: "concat" };
2
+ var DEFAULT_OPTIONS = { array: "replace" };
3
3
  var mix = createMix();
4
+ var concatMix = createMix({ array: "concat" });
4
5
  function createMix(userOptions = {}) {
5
6
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
6
7
  function _mix(...sources) {
@@ -82,6 +83,7 @@ function objectType(value) {
82
83
  return Object.prototype.toString.call(value);
83
84
  }
84
85
  export {
86
+ concatMix,
85
87
  createMix,
86
88
  mix
87
89
  };
@@ -1,6 +1,6 @@
1
1
  // src/node/file-cache.js
2
2
  import fs from "fs/promises";
3
- import glob from "glob-promise";
3
+ import { glob } from "glob";
4
4
  import path from "path";
5
5
 
6
6
  // src/lib/objects/nested-property.js
@@ -17,7 +17,7 @@ function dirname(url) {
17
17
 
18
18
  // src/node/file-cache.js
19
19
  import fs from "fs/promises";
20
- import glob from "glob-promise";
20
+ import { glob } from "glob";
21
21
  import path2 from "path";
22
22
 
23
23
  // src/lib/objects/nested-property.js
@@ -17,6 +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
21
  export { PKCE } from './pkce.cjs';
21
22
  export { queryParams } from './query-params.cjs';
22
23
  export { randomString, uuid } from './random-string.cjs';
@@ -1,3 +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';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Scroll Observer - Optimized for performance
3
+ * @param {Element} node - The element to observe scroll events on
4
+ * @param {Object} options - Configuration options
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
13
+ */
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
+ }): {
24
+ destroy(): void;
25
+ };
26
+
27
+ export { scrollObserver };
@@ -1,6 +1,72 @@
1
- declare function curry(fn: any): (...args: any[]) => any;
2
- declare function compose(...fns: any[]): (value: any) => any;
3
- declare function pipe(...fns: any[]): (value: any) => any;
4
- declare function times(fn: any, n: any): any[];
1
+ /**
2
+ * Creates a curried version of a function that can be called with partial arguments
3
+ * @param {Function} fn - The function to curry
4
+ * @return {Function} The curried function
5
+ * @property {any} return - The result of calling the original function when all arguments are provided
6
+ * @example
7
+ * const add = (a, b, c) => a + b + c
8
+ * const curriedAdd = curry(add)
9
+ * curriedAdd(1)(2)(3) // 6
10
+ * curriedAdd(1, 2)(3) // 6
11
+ * curriedAdd(1)(2, 3) // 6
12
+ */
13
+ declare function curry(fn: Function): Function;
14
+ /**
15
+ * Composes functions from right to left (synchronous)
16
+ * @param {...Function} fns - Functions to compose
17
+ * @return {Function} The composed function
18
+ * @property {any} return - The result of applying all functions in sequence
19
+ * @example
20
+ * const add1 = x => x + 1
21
+ * const multiply2 = x => x * 2
22
+ * const composed = compose(add1, multiply2)
23
+ * composed(3) // 7 (3 * 2 + 1)
24
+ */
25
+ declare function compose(...fns: Function[]): Function;
26
+ /**
27
+ * Composes functions from right to left (asynchronous)
28
+ * @param {...Function} fns - Functions to compose (can be sync or async)
29
+ * @return {Function} The composed async function
30
+ * @property {Promise<any>} return - A Promise that resolves to the result of applying all functions
31
+ * @example
32
+ * const add1 = x => x + 1
33
+ * const multiplyAsync = async x => x * 2
34
+ * await composeAsync(add1, multiplyAsync)(3) // 7 (3 * 2 + 1)
35
+ */
36
+ declare function composeAsync(...fns: Function[]): Function;
37
+ /**
38
+ * Pipes functions from left to right (synchronous)
39
+ * @param {...Function} fns - Functions to pipe
40
+ * @return {Function} The piped function
41
+ * @property {any} return - The result of applying all functions in sequence
42
+ * @example
43
+ * const add1 = x => x + 1
44
+ * const multiply2 = x => x * 2
45
+ * const piped = pipe(add1, multiply2)
46
+ * piped(3) // 8 (3 + 1) * 2
47
+ */
48
+ declare function pipe(...fns: Function[]): Function;
49
+ /**
50
+ * Pipes functions from left to right (asynchronous)
51
+ * @param {...Function} fns - Functions to pipe (can be sync or async)
52
+ * @return {Function} The piped async function
53
+ * @property {Promise<any>} return - A Promise that resolves to the result of applying all functions
54
+ * @example
55
+ * const add1 = x => x + 1
56
+ * const multiplyAsync = async x => x * 2
57
+ * await pipeAsync(add1, multiplyAsync)(3) // 8 ((3 + 1) * 2)
58
+ */
59
+ declare function pipeAsync(...fns: Function[]): Function;
60
+ /**
61
+ * Calls a function n times with the current index and returns an array of results
62
+ * @param {Function} fn - Function to call (receives index as argument)
63
+ * @param {number} n - Number of times to call the function
64
+ * @return {any[]} Array of results from calling the function
65
+ * @property {any[]} return - Array containing the results of each function call
66
+ * @example
67
+ * times(i => i * 2, 3) // [0, 2, 4]
68
+ * times(() => Math.random(), 2) // [0.123, 0.456] (random values)
69
+ */
70
+ declare function times(fn: Function, n: number): any[];
5
71
 
6
- export { compose, curry, pipe, times };
72
+ export { compose, composeAsync, curry, pipe, pipeAsync, times };
@@ -1,5 +1,5 @@
1
1
  export { debounce } from './debounce.cjs';
2
2
  export { getEnv } from './env.cjs';
3
- export { compose, curry, pipe, times } from './functional.cjs';
3
+ export { compose, composeAsync, curry, pipe, pipeAsync, times } from './functional.cjs';
4
4
  export { throttle } from './throttle.cjs';
5
5
  export { delay, timeout, wait } from './timeout.cjs';
@@ -13,7 +13,7 @@ export { flattenArrayFields, formDataToObject } from './form/form-data.cjs';
13
13
  export { SanitizeOptions, sanitize, sanitizeArray, sanitizeObject } from './form/sanitize.cjs';
14
14
  export { debounce } from './functions/debounce.cjs';
15
15
  export { getEnv } from './functions/env.cjs';
16
- export { compose, curry, pipe, times } from './functions/functional.cjs';
16
+ export { compose, composeAsync, curry, pipe, pipeAsync, times } from './functions/functional.cjs';
17
17
  export { throttle } from './functions/throttle.cjs';
18
18
  export { delay, timeout, wait } from './functions/timeout.cjs';
19
19
  export { isHashedValue } from './hash.cjs';
@@ -25,7 +25,7 @@ export { extendObject } from './objects/extend.cjs';
25
25
  export { flattenObject } from './objects/flatten.cjs';
26
26
  export { parseJSON } from './objects/json.cjs';
27
27
  export { objectForEach, objectMap } from './objects/loop.cjs';
28
- export { createMix, mix } from './objects/mix/mix.cjs';
28
+ export { concatMix, createMix, mix } from './objects/mix/mix.cjs';
29
29
  export { getNestedProperty, getNestedProperty2, getNestedValue } from './objects/nested-property.cjs';
30
30
  export { normalizeObject } from './objects/normalize-object.cjs';
31
31
  export { omitEmpty } from './objects/omit-empty.cjs';
@@ -1,6 +1,6 @@
1
1
  declare function extendObject(object: any): {
2
2
  orig: any;
3
- extended: {};
3
+ extended: any;
4
4
  };
5
5
 
6
6
  export { extendObject };
@@ -5,7 +5,7 @@ export { extendObject } from './extend.cjs';
5
5
  export { flattenObject } from './flatten.cjs';
6
6
  export { parseJSON } from './json.cjs';
7
7
  export { objectForEach, objectMap } from './loop.cjs';
8
- export { createMix, mix } from './mix/mix.cjs';
8
+ export { concatMix, createMix, mix } from './mix/mix.cjs';
9
9
  export { getNestedProperty, getNestedProperty2, getNestedValue } from './nested-property.cjs';
10
10
  export { normalizeObject } from './normalize-object.cjs';
11
11
  export { omitEmpty } from './omit-empty.cjs';
@@ -1,4 +1,44 @@
1
- declare function createMix(userOptions?: {}): (...sources: any[]) => {};
2
- declare function mix(...sources: any[]): {};
1
+ /**
2
+ * Creates a customized mix function with specific options for array handling.
3
+ *
4
+ * @param {Object} [userOptions={}] - Configuration options for the mix function.
5
+ * @param {('replace'|'concat')} [userOptions.array='replace'] - How to handle array merging:
6
+ * - 'replace': Arrays from later sources completely replace arrays from earlier sources
7
+ * - 'concat': Arrays are concatenated together
8
+ * @return {Function} A mix function configured with the provided options.
9
+ *
10
+ * @example
11
+ * // Create mix function that concatenates arrays
12
+ * const concatMix = createMix({ array: 'concat' })
13
+ * const obj1 = { items: [1, 2] }
14
+ * const obj2 = { items: [3, 4] }
15
+ * const result = concatMix(obj1, obj2)
16
+ * // Returns: { items: [1, 2, 3, 4] }
17
+ *
18
+ * @example
19
+ * // Create mix function that replaces arrays (default behavior)
20
+ * const replaceMix = createMix({ array: 'replace' })
21
+ * const obj1 = { items: [1, 2] }
22
+ * const obj2 = { items: [3, 4] }
23
+ * const result = replaceMix(obj1, obj2)
24
+ * // Returns: { items: [3, 4] }
25
+ */
26
+ declare function createMix(userOptions?: {
27
+ array?: ("replace" | "concat");
28
+ }): Function;
29
+ /**
30
+ * Deep merges multiple objects into a new object with intelligent handling of arrays and nested objects.
31
+ *
32
+ * @param {...Object} sources - Objects to merge. Null and undefined values are skipped.
33
+ * @return {Object} A new object containing the merged properties from all sources.
34
+ *
35
+ * @example
36
+ * const obj1 = { a: 1, b: { x: 10 } }
37
+ * const obj2 = { b: { y: 20 }, c: 3 }
38
+ * const result = mix(obj1, obj2)
39
+ * // Returns: { a: 1, b: { x: 10, y: 20 }, c: 3 }
40
+ */
41
+ declare const mix: Function;
42
+ declare const concatMix: Function;
3
43
 
4
- export { createMix, mix };
44
+ export { concatMix, createMix, mix };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splendidlabz/utils",
3
- "version": "1.7.0",
3
+ "version": "1.8.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -53,7 +53,7 @@
53
53
  "dependencies": {
54
54
  "deep-eql": "^5.0.2",
55
55
  "dompurify": "^3.2.4",
56
- "glob-promise": "^6.0.7",
56
+ "glob": "^11.0.0",
57
57
  "marked": "^15.0.7",
58
58
  "marked-gfm-heading-id": "^4.1.1",
59
59
  "marked-mangle": "^1.1.10",
@@ -63,7 +63,6 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@splendidlabz/eslint-config": "2.1.0",
66
- "glob": "^11.0.0",
67
66
  "jsdom": "^26.0.0",
68
67
  "np": "^10.2.0",
69
68
  "tsup": "^8.0.0",
@@ -1,3 +1,4 @@
1
1
  export * from './intersection-observer.js'
2
2
  export * from './mutation-observer.js'
3
3
  export * from './resize-observer.js'
4
+ export * from './scroll-observer.js'
@@ -4,8 +4,8 @@ import { useObserverMethodOnTarget } from './observer.js'
4
4
 
5
5
  /**
6
6
  * Creates and manages a ResizeObserver instance to monitor size changes of a target element.
7
- *
8
- * @param {Element|Window|NodeList|Element[]} target - The element(s) to observe.
7
+ *
8
+ * @param {Element|Window|NodeList|Element[]} target - The element(s) to observe.
9
9
  * - If window is provided, document.body 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
@@ -13,13 +13,13 @@ import { useObserverMethodOnTarget } from './observer.js'
13
13
  * @param {Function} [options.callback] - Optional callback function that will be called when resize changes are detected.
14
14
  * If not provided, a 'resize-obs' event will be dispatched on the target.
15
15
  * @param {Object} [options.observerOptions] - Additional options to pass to ResizeObserver.observe()
16
- *
16
+ *
17
17
  * @returns {Object} An object with methods to control the observer:
18
18
  * - observe(target, options): Start observing a new target element
19
19
  * - unobserve(target): Stop observing a target element
20
20
  * - disconnect(): Disconnect the observer and stop all observations
21
21
  * - destroy(): Alias for disconnect()
22
- *
22
+ *
23
23
  * @example
24
24
  * // Basic usage with callback
25
25
  * resizeObserver(element, {
@@ -27,7 +27,7 @@ import { useObserverMethodOnTarget } from './observer.js'
27
27
  * console.log('Element resized:', entry.contentRect);
28
28
  * }
29
29
  * });
30
- *
30
+ *
31
31
  * @example
32
32
  * // Usage with event listener
33
33
  * resizeObserver(element);
@@ -0,0 +1,132 @@
1
+ /* eslint-env browser */
2
+ import { resizeObserver } from './resize-observer.js'
3
+
4
+ const defaultOptions = {
5
+ threshold: 0, // Float between 0 to 1.
6
+ tolerance: 0.1, // Float between 0 to 1. Tolerance for event firing
7
+ throttle: 16, // Throttle interval in ms (default: ~60fps)
8
+ once: false, // Only fire threshold callback once
9
+ }
10
+
11
+ // TODO: Move to Utils/Dom
12
+ /**
13
+ * Scroll Observer - Optimized for performance
14
+ * @param {Element} node - The element to observe scroll events on
15
+ * @param {Object} options - Configuration options
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
24
+ */
25
+ export function scrollObserver(node, options = {}) {
26
+ const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } =
27
+ options
28
+ const opts = { ...defaultOptions, ...userOpts }
29
+ const { threshold, tolerance, throttle, once } = opts
30
+
31
+ const prevScrollDirection = null
32
+ let prevScrollTop = 0
33
+ let prevScrollPercent = 0
34
+ let lastThrottleTime = 0
35
+ let thresholdFired = false
36
+ let rafId = null
37
+
38
+ // Determine scroll context once at initialization
39
+ const isDocumentScroll = node === document || node === window
40
+ const scrollElement = isDocumentScroll ? document.documentElement : node
41
+
42
+ // Cache DOM references and expensive calculations
43
+ let cachedScrollHeight = 0
44
+ let cachedClientHeight = 0
45
+
46
+ // Initialize cache and start observing for changes
47
+ updateCache()
48
+ const cacheObserver = resizeObserver(scrollElement, {
49
+ callback: updateCache,
50
+ })
51
+
52
+ node.addEventListener('scroll', throttledObserve, { passive: true })
53
+
54
+ function updateCache() {
55
+ cachedScrollHeight = scrollElement.scrollHeight
56
+ cachedClientHeight = scrollElement.clientHeight
57
+ }
58
+
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
+ function observe() {
68
+ const scrollTop = scrollElement.scrollTop
69
+
70
+ // Skip if scroll position hasn't changed meaningfully
71
+ if (Math.abs(scrollTop - prevScrollTop) < 1) return
72
+
73
+ const scrollDirection = scrollTop > prevScrollTop ? 'down' : 'up'
74
+ const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight)
75
+ const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll))
76
+
77
+ // Check threshold crossing (e.g., 75% ± tolerance)
78
+ const thresholdMin = threshold - tolerance / 2
79
+ const thresholdMax = threshold + tolerance / 2
80
+ const wasInThreshold =
81
+ prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax
82
+ const isInThreshold =
83
+ scrollPercent >= thresholdMin && scrollPercent <= thresholdMax
84
+ const hasEnteredThreshold =
85
+ !wasInThreshold && isInThreshold && (!once || !thresholdFired)
86
+
87
+ if (hasEnteredThreshold && once) {
88
+ thresholdFired = true
89
+ }
90
+
91
+ // Prepare common data for all callbacks
92
+ const callbackData = {
93
+ scrollTop,
94
+ scrollDirection,
95
+ scrollPercent,
96
+ directionChanged: scrollDirection !== prevScrollDirection,
97
+ hasEnteredThreshold,
98
+ isInThreshold,
99
+ }
100
+
101
+ // Call specific callbacks
102
+ if (typeof callback === 'function') {
103
+ callback(callbackData)
104
+ }
105
+
106
+ // Only fire direction callbacks when direction actually changes
107
+ if (scrollDirection !== prevScrollDirection) {
108
+ if (scrollDirection === 'down' && typeof onScrollDown === 'function') {
109
+ onScrollDown(callbackData)
110
+ }
111
+ if (scrollDirection === 'up' && typeof onScrollUp === 'function') {
112
+ onScrollUp(callbackData)
113
+ }
114
+ }
115
+
116
+ // Fire threshold callback when entering threshold zone
117
+ if (hasEnteredThreshold && typeof onEnterThreshold === 'function') {
118
+ onEnterThreshold(callbackData)
119
+ }
120
+
121
+ prevScrollTop = scrollTop
122
+ prevScrollPercent = scrollPercent
123
+ }
124
+
125
+ return {
126
+ destroy() {
127
+ node.removeEventListener('scroll', throttledObserve)
128
+ if (rafId) cancelAnimationFrame(rafId)
129
+ cacheObserver.destroy()
130
+ },
131
+ }
132
+ }
@@ -16,10 +16,12 @@ export function ms(arg) {
16
16
  if (unit === 'hours') return getTimeInMs(value, 'hours')
17
17
  if (unit === 'm') return getTimeInMs(value, 'minutes')
18
18
  if (unit === 'min') return getTimeInMs(value, 'minutes')
19
+ if (unit === 'mins') return getTimeInMs(value, 'minutes')
19
20
  if (unit === 'minute') return getTimeInMs(value, 'minutes')
20
21
  if (unit === 'minutes') return getTimeInMs(value, 'minutes')
21
22
  if (unit === 's') return getTimeInMs(value, 'seconds')
22
23
  if (unit === 'sec') return getTimeInMs(value, 'seconds')
24
+ if (unit === 'secs') return getTimeInMs(value, 'seconds')
23
25
  if (unit === 'second') return getTimeInMs(value, 'seconds')
24
26
  if (unit === 'seconds') return getTimeInMs(value, 'seconds')
25
27
 
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Creates a curried version of a function that can be called with partial arguments
3
+ * @param {Function} fn - The function to curry
4
+ * @return {Function} The curried function
5
+ * @property {any} return - The result of calling the original function when all arguments are provided
6
+ * @example
7
+ * const add = (a, b, c) => a + b + c
8
+ * const curriedAdd = curry(add)
9
+ * curriedAdd(1)(2)(3) // 6
10
+ * curriedAdd(1, 2)(3) // 6
11
+ * curriedAdd(1)(2, 3) // 6
12
+ */
1
13
  export function curry(fn) {
2
14
  return function curried(...args) {
3
15
  if (args.length >= fn.length) {
@@ -10,18 +22,88 @@ export function curry(fn) {
10
22
  }
11
23
  }
12
24
 
25
+ /**
26
+ * Composes functions from right to left (synchronous)
27
+ * @param {...Function} fns - Functions to compose
28
+ * @return {Function} The composed function
29
+ * @property {any} return - The result of applying all functions in sequence
30
+ * @example
31
+ * const add1 = x => x + 1
32
+ * const multiply2 = x => x * 2
33
+ * const composed = compose(add1, multiply2)
34
+ * composed(3) // 7 (3 * 2 + 1)
35
+ */
13
36
  export function compose(...fns) {
14
37
  return function (value) {
15
38
  return fns.reduceRight((acc, fn) => fn(acc), value)
16
39
  }
17
40
  }
18
41
 
42
+ /**
43
+ * Composes functions from right to left (asynchronous)
44
+ * @param {...Function} fns - Functions to compose (can be sync or async)
45
+ * @return {Function} The composed async function
46
+ * @property {Promise<any>} return - A Promise that resolves to the result of applying all functions
47
+ * @example
48
+ * const add1 = x => x + 1
49
+ * const multiplyAsync = async x => x * 2
50
+ * await composeAsync(add1, multiplyAsync)(3) // 7 (3 * 2 + 1)
51
+ */
52
+ export function composeAsync(...fns) {
53
+ return async function (value) {
54
+ return fns.reduceRight(async (acc, fn) => {
55
+ const result = await acc
56
+ return await fn(result)
57
+ }, value)
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Pipes functions from left to right (synchronous)
63
+ * @param {...Function} fns - Functions to pipe
64
+ * @return {Function} The piped function
65
+ * @property {any} return - The result of applying all functions in sequence
66
+ * @example
67
+ * const add1 = x => x + 1
68
+ * const multiply2 = x => x * 2
69
+ * const piped = pipe(add1, multiply2)
70
+ * piped(3) // 8 (3 + 1) * 2
71
+ */
19
72
  export function pipe(...fns) {
20
73
  return function (value) {
21
74
  return fns.reduce((acc, fn) => fn(acc), value)
22
75
  }
23
76
  }
24
77
 
78
+ /**
79
+ * Pipes functions from left to right (asynchronous)
80
+ * @param {...Function} fns - Functions to pipe (can be sync or async)
81
+ * @return {Function} The piped async function
82
+ * @property {Promise<any>} return - A Promise that resolves to the result of applying all functions
83
+ * @example
84
+ * const add1 = x => x + 1
85
+ * const multiplyAsync = async x => x * 2
86
+ * await pipeAsync(add1, multiplyAsync)(3) // 8 ((3 + 1) * 2)
87
+ */
88
+ export function pipeAsync(...fns) {
89
+ return async function (value) {
90
+ return fns.reduce(async (acc, fn) => {
91
+ const result = await acc
92
+ return await fn(result)
93
+ }, value)
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Calls a function n times with the current index and returns an array of results
99
+ * @param {Function} fn - Function to call (receives index as argument)
100
+ * @param {number} n - Number of times to call the function
101
+ * @return {any[]} Array of results from calling the function
102
+ * @property {any[]} return - Array containing the results of each function call
103
+ * @example
104
+ * times(i => i * 2, 3) // [0, 2, 4]
105
+ * times(() => Math.random(), 2) // [0.123, 0.456] (random values)
106
+ */
25
107
  export function times(fn, n) {
26
108
  const result = []
27
109
  for (let i = 0; i < n; i++) {