@splendidlabz/utils 1.8.1 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @splendidlabz/utils
2
2
 
3
+ ## 1.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - - abd0770c Add pipeAsync and composeAsync along with tests
8
+
3
9
  ## 1.8.1
4
10
 
5
11
  ### Patch Changes
@@ -79,6 +79,7 @@ __export(dom_exports, {
79
79
  removeListeners: () => removeListeners,
80
80
  resizeObserver: () => resizeObserver,
81
81
  sanitize: () => sanitize2,
82
+ scrollObserver: () => scrollObserver,
82
83
  scrollToElement: () => scrollToElement,
83
84
  sessionStore: () => sessionStore,
84
85
  setCSSValue: () => setCSSValue,
@@ -943,6 +944,94 @@ function mutationObserver(target, options) {
943
944
  };
944
945
  }
945
946
 
947
+ // src/dom/observers/scroll-observer.js
948
+ var defaultOptions2 = {
949
+ threshold: 0,
950
+ // Float between 0 to 1.
951
+ tolerance: 0.1,
952
+ // Float between 0 to 1. Tolerance for event firing
953
+ throttle: 16,
954
+ // Throttle interval in ms (default: ~60fps)
955
+ once: false
956
+ // Only fire threshold callback once
957
+ };
958
+ function scrollObserver(node, options = {}) {
959
+ const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
960
+ const opts = { ...defaultOptions2, ...userOpts };
961
+ const { threshold, tolerance, throttle, once } = opts;
962
+ const prevScrollDirection = null;
963
+ let prevScrollTop = 0;
964
+ let prevScrollPercent = 0;
965
+ let lastThrottleTime = 0;
966
+ let thresholdFired = false;
967
+ let rafId = null;
968
+ const isDocumentScroll = node === document || node === window;
969
+ const scrollElement = isDocumentScroll ? document.documentElement : node;
970
+ let cachedScrollHeight = 0;
971
+ let cachedClientHeight = 0;
972
+ updateCache();
973
+ const cacheObserver = resizeObserver(scrollElement, {
974
+ callback: updateCache
975
+ });
976
+ node.addEventListener("scroll", throttledObserve, { passive: true });
977
+ function updateCache() {
978
+ cachedScrollHeight = scrollElement.scrollHeight;
979
+ cachedClientHeight = scrollElement.clientHeight;
980
+ }
981
+ function throttledObserve() {
982
+ const now = Date.now();
983
+ if (now - lastThrottleTime < throttle) return;
984
+ lastThrottleTime = now;
985
+ rafId = requestAnimationFrame(observe);
986
+ }
987
+ function observe() {
988
+ const scrollTop = scrollElement.scrollTop;
989
+ if (Math.abs(scrollTop - prevScrollTop) < 1) return;
990
+ const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
991
+ const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
992
+ const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
993
+ const thresholdMin = threshold - tolerance / 2;
994
+ const thresholdMax = threshold + tolerance / 2;
995
+ const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
996
+ const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
997
+ const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
998
+ if (hasEnteredThreshold && once) {
999
+ thresholdFired = true;
1000
+ }
1001
+ const callbackData = {
1002
+ scrollTop,
1003
+ scrollDirection,
1004
+ scrollPercent,
1005
+ directionChanged: scrollDirection !== prevScrollDirection,
1006
+ hasEnteredThreshold,
1007
+ isInThreshold
1008
+ };
1009
+ if (typeof callback === "function") {
1010
+ callback(callbackData);
1011
+ }
1012
+ if (scrollDirection !== prevScrollDirection) {
1013
+ if (scrollDirection === "down" && typeof onScrollDown === "function") {
1014
+ onScrollDown(callbackData);
1015
+ }
1016
+ if (scrollDirection === "up" && typeof onScrollUp === "function") {
1017
+ onScrollUp(callbackData);
1018
+ }
1019
+ }
1020
+ if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
1021
+ onEnterThreshold(callbackData);
1022
+ }
1023
+ prevScrollTop = scrollTop;
1024
+ prevScrollPercent = scrollPercent;
1025
+ }
1026
+ return {
1027
+ destroy() {
1028
+ node.removeEventListener("scroll", throttledObserve);
1029
+ if (rafId) cancelAnimationFrame(rafId);
1030
+ cacheObserver.destroy();
1031
+ }
1032
+ };
1033
+ }
1034
+
946
1035
  // src/dom/random-string.js
947
1036
  function randomString(length = 10) {
948
1037
  const firstLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
@@ -1182,6 +1271,7 @@ function scrambleText(text) {
1182
1271
  removeListeners,
1183
1272
  resizeObserver,
1184
1273
  sanitize,
1274
+ scrollObserver,
1185
1275
  scrollToElement,
1186
1276
  sessionStore,
1187
1277
  setCSSValue,
@@ -21,7 +21,8 @@ var observers_exports = {};
21
21
  __export(observers_exports, {
22
22
  intersectionObserver: () => intersectionObserver,
23
23
  mutationObserver: () => mutationObserver,
24
- resizeObserver: () => resizeObserver
24
+ resizeObserver: () => resizeObserver,
25
+ scrollObserver: () => scrollObserver
25
26
  });
26
27
  module.exports = __toCommonJS(observers_exports);
27
28
 
@@ -148,9 +149,98 @@ function resizeObserver(target, options) {
148
149
  destroy: (_) => observer.disconnect()
149
150
  };
150
151
  }
152
+
153
+ // src/dom/observers/scroll-observer.js
154
+ var defaultOptions2 = {
155
+ threshold: 0,
156
+ // Float between 0 to 1.
157
+ tolerance: 0.1,
158
+ // Float between 0 to 1. Tolerance for event firing
159
+ throttle: 16,
160
+ // Throttle interval in ms (default: ~60fps)
161
+ once: false
162
+ // Only fire threshold callback once
163
+ };
164
+ function scrollObserver(node, options = {}) {
165
+ const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
166
+ const opts = { ...defaultOptions2, ...userOpts };
167
+ const { threshold, tolerance, throttle, once } = opts;
168
+ const prevScrollDirection = null;
169
+ let prevScrollTop = 0;
170
+ let prevScrollPercent = 0;
171
+ let lastThrottleTime = 0;
172
+ let thresholdFired = false;
173
+ let rafId = null;
174
+ const isDocumentScroll = node === document || node === window;
175
+ const scrollElement = isDocumentScroll ? document.documentElement : node;
176
+ let cachedScrollHeight = 0;
177
+ let cachedClientHeight = 0;
178
+ updateCache();
179
+ const cacheObserver = resizeObserver(scrollElement, {
180
+ callback: updateCache
181
+ });
182
+ node.addEventListener("scroll", throttledObserve, { passive: true });
183
+ function updateCache() {
184
+ cachedScrollHeight = scrollElement.scrollHeight;
185
+ cachedClientHeight = scrollElement.clientHeight;
186
+ }
187
+ function throttledObserve() {
188
+ const now = Date.now();
189
+ if (now - lastThrottleTime < throttle) return;
190
+ lastThrottleTime = now;
191
+ rafId = requestAnimationFrame(observe);
192
+ }
193
+ function observe() {
194
+ const scrollTop = scrollElement.scrollTop;
195
+ if (Math.abs(scrollTop - prevScrollTop) < 1) return;
196
+ const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
197
+ const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
198
+ const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
199
+ const thresholdMin = threshold - tolerance / 2;
200
+ const thresholdMax = threshold + tolerance / 2;
201
+ const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
202
+ const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
203
+ const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
204
+ if (hasEnteredThreshold && once) {
205
+ thresholdFired = true;
206
+ }
207
+ const callbackData = {
208
+ scrollTop,
209
+ scrollDirection,
210
+ scrollPercent,
211
+ directionChanged: scrollDirection !== prevScrollDirection,
212
+ hasEnteredThreshold,
213
+ isInThreshold
214
+ };
215
+ if (typeof callback === "function") {
216
+ callback(callbackData);
217
+ }
218
+ if (scrollDirection !== prevScrollDirection) {
219
+ if (scrollDirection === "down" && typeof onScrollDown === "function") {
220
+ onScrollDown(callbackData);
221
+ }
222
+ if (scrollDirection === "up" && typeof onScrollUp === "function") {
223
+ onScrollUp(callbackData);
224
+ }
225
+ }
226
+ if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
227
+ onEnterThreshold(callbackData);
228
+ }
229
+ prevScrollTop = scrollTop;
230
+ prevScrollPercent = scrollPercent;
231
+ }
232
+ return {
233
+ destroy() {
234
+ node.removeEventListener("scroll", throttledObserve);
235
+ if (rafId) cancelAnimationFrame(rafId);
236
+ cacheObserver.destroy();
237
+ }
238
+ };
239
+ }
151
240
  // Annotate the CommonJS export names for ESM import in node:
152
241
  0 && (module.exports = {
153
242
  intersectionObserver,
154
243
  mutationObserver,
155
- resizeObserver
244
+ resizeObserver,
245
+ scrollObserver
156
246
  });
@@ -0,0 +1,170 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/dom/observers/scroll-observer.js
20
+ var scroll_observer_exports = {};
21
+ __export(scroll_observer_exports, {
22
+ scrollObserver: () => scrollObserver
23
+ });
24
+ module.exports = __toCommonJS(scroll_observer_exports);
25
+
26
+ // src/dom/events.js
27
+ function dispatchEvent(node, eventName, detail, options = {}) {
28
+ node.dispatchEvent(
29
+ new CustomEvent(eventName, {
30
+ ...options,
31
+ detail
32
+ })
33
+ );
34
+ }
35
+
36
+ // src/dom/get-element.js
37
+ function getNodeType(node) {
38
+ if (node instanceof Element) return "element";
39
+ if (node instanceof NodeList) return "nodelist";
40
+ if (Array.isArray(node)) return "array";
41
+ }
42
+
43
+ // src/dom/observers/observer.js
44
+ function useObserverMethodOnTarget(target, observer, method = "observe", options = void 0) {
45
+ const targetType = getNodeType(target);
46
+ if (targetType === "element") observer[method](target, options);
47
+ if (targetType === "nodelist") {
48
+ const elements = Array.from(target);
49
+ elements.forEach((element) => observer[method](element, options));
50
+ }
51
+ if (targetType === "array") {
52
+ target.forEach((element) => observer[method](element, options));
53
+ }
54
+ }
55
+
56
+ // src/dom/observers/resize-observer.js
57
+ function resizeObserver(target, options) {
58
+ const { callback, ...opts } = options;
59
+ const observer = new ResizeObserver(observerFn);
60
+ if (target === window) target = document.body;
61
+ useObserverMethodOnTarget(target, observer, "observe", opts);
62
+ function observerFn(entries) {
63
+ for (const entry of entries) {
64
+ if (callback) callback({ entry, entries, observer });
65
+ else dispatchEvent(target, "resize-obs", { entry, entries, observer });
66
+ }
67
+ }
68
+ return {
69
+ observe(target2) {
70
+ useObserverMethodOnTarget(target2, observer, "observe", options);
71
+ },
72
+ unobserve(target2) {
73
+ useObserverMethodOnTarget(target2, observer, "unobserve");
74
+ },
75
+ disconnect: (_) => observer.disconnect(),
76
+ destroy: (_) => observer.disconnect()
77
+ };
78
+ }
79
+
80
+ // src/dom/observers/scroll-observer.js
81
+ var defaultOptions = {
82
+ threshold: 0,
83
+ // Float between 0 to 1.
84
+ tolerance: 0.1,
85
+ // Float between 0 to 1. Tolerance for event firing
86
+ throttle: 16,
87
+ // Throttle interval in ms (default: ~60fps)
88
+ once: false
89
+ // Only fire threshold callback once
90
+ };
91
+ function scrollObserver(node, options = {}) {
92
+ const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
93
+ const opts = { ...defaultOptions, ...userOpts };
94
+ const { threshold, tolerance, throttle, once } = opts;
95
+ const prevScrollDirection = null;
96
+ let prevScrollTop = 0;
97
+ let prevScrollPercent = 0;
98
+ let lastThrottleTime = 0;
99
+ let thresholdFired = false;
100
+ let rafId = null;
101
+ const isDocumentScroll = node === document || node === window;
102
+ const scrollElement = isDocumentScroll ? document.documentElement : node;
103
+ let cachedScrollHeight = 0;
104
+ let cachedClientHeight = 0;
105
+ updateCache();
106
+ const cacheObserver = resizeObserver(scrollElement, {
107
+ callback: updateCache
108
+ });
109
+ node.addEventListener("scroll", throttledObserve, { passive: true });
110
+ function updateCache() {
111
+ cachedScrollHeight = scrollElement.scrollHeight;
112
+ cachedClientHeight = scrollElement.clientHeight;
113
+ }
114
+ function throttledObserve() {
115
+ const now = Date.now();
116
+ if (now - lastThrottleTime < throttle) return;
117
+ lastThrottleTime = now;
118
+ rafId = requestAnimationFrame(observe);
119
+ }
120
+ function observe() {
121
+ const scrollTop = scrollElement.scrollTop;
122
+ if (Math.abs(scrollTop - prevScrollTop) < 1) return;
123
+ const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
124
+ const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
125
+ const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
126
+ const thresholdMin = threshold - tolerance / 2;
127
+ const thresholdMax = threshold + tolerance / 2;
128
+ const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
129
+ const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
130
+ const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
131
+ if (hasEnteredThreshold && once) {
132
+ thresholdFired = true;
133
+ }
134
+ const callbackData = {
135
+ scrollTop,
136
+ scrollDirection,
137
+ scrollPercent,
138
+ directionChanged: scrollDirection !== prevScrollDirection,
139
+ hasEnteredThreshold,
140
+ isInThreshold
141
+ };
142
+ if (typeof callback === "function") {
143
+ callback(callbackData);
144
+ }
145
+ if (scrollDirection !== prevScrollDirection) {
146
+ if (scrollDirection === "down" && typeof onScrollDown === "function") {
147
+ onScrollDown(callbackData);
148
+ }
149
+ if (scrollDirection === "up" && typeof onScrollUp === "function") {
150
+ onScrollUp(callbackData);
151
+ }
152
+ }
153
+ if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
154
+ onEnterThreshold(callbackData);
155
+ }
156
+ prevScrollTop = scrollTop;
157
+ prevScrollPercent = scrollPercent;
158
+ }
159
+ return {
160
+ destroy() {
161
+ node.removeEventListener("scroll", throttledObserve);
162
+ if (rafId) cancelAnimationFrame(rafId);
163
+ cacheObserver.destroy();
164
+ }
165
+ };
166
+ }
167
+ // Annotate the CommonJS export names for ESM import in node:
168
+ 0 && (module.exports = {
169
+ scrollObserver
170
+ });
@@ -20,8 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var functional_exports = {};
21
21
  __export(functional_exports, {
22
22
  compose: () => compose,
23
+ composeAsync: () => composeAsync,
23
24
  curry: () => curry,
24
25
  pipe: () => pipe,
26
+ pipeAsync: () => pipeAsync,
25
27
  times: () => times
26
28
  });
27
29
  module.exports = __toCommonJS(functional_exports);
@@ -41,11 +43,27 @@ function compose(...fns) {
41
43
  return fns.reduceRight((acc, fn) => fn(acc), value);
42
44
  };
43
45
  }
46
+ function composeAsync(...fns) {
47
+ return async function(value) {
48
+ return fns.reduceRight(async (acc, fn) => {
49
+ const result = await acc;
50
+ return await fn(result);
51
+ }, value);
52
+ };
53
+ }
44
54
  function pipe(...fns) {
45
55
  return function(value) {
46
56
  return fns.reduce((acc, fn) => fn(acc), value);
47
57
  };
48
58
  }
59
+ function pipeAsync(...fns) {
60
+ return async function(value) {
61
+ return fns.reduce(async (acc, fn) => {
62
+ const result = await acc;
63
+ return await fn(result);
64
+ }, value);
65
+ };
66
+ }
49
67
  function times(fn, n) {
50
68
  const result = [];
51
69
  for (let i = 0; i < n; i++) {
@@ -56,7 +74,9 @@ function times(fn, n) {
56
74
  // Annotate the CommonJS export names for ESM import in node:
57
75
  0 && (module.exports = {
58
76
  compose,
77
+ composeAsync,
59
78
  curry,
60
79
  pipe,
80
+ pipeAsync,
61
81
  times
62
82
  });
@@ -20,11 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var functions_exports = {};
21
21
  __export(functions_exports, {
22
22
  compose: () => compose,
23
+ composeAsync: () => composeAsync,
23
24
  curry: () => curry,
24
25
  debounce: () => debounce,
25
26
  delay: () => delay,
26
27
  getEnv: () => getEnv,
27
28
  pipe: () => pipe,
29
+ pipeAsync: () => pipeAsync,
28
30
  throttle: () => throttle,
29
31
  timeout: () => timeout,
30
32
  times: () => times,
@@ -72,11 +74,27 @@ function compose(...fns) {
72
74
  return fns.reduceRight((acc, fn) => fn(acc), value);
73
75
  };
74
76
  }
77
+ function composeAsync(...fns) {
78
+ return async function(value) {
79
+ return fns.reduceRight(async (acc, fn) => {
80
+ const result = await acc;
81
+ return await fn(result);
82
+ }, value);
83
+ };
84
+ }
75
85
  function pipe(...fns) {
76
86
  return function(value) {
77
87
  return fns.reduce((acc, fn) => fn(acc), value);
78
88
  };
79
89
  }
90
+ function pipeAsync(...fns) {
91
+ return async function(value) {
92
+ return fns.reduce(async (acc, fn) => {
93
+ const result = await acc;
94
+ return await fn(result);
95
+ }, value);
96
+ };
97
+ }
80
98
  function times(fn, n) {
81
99
  const result = [];
82
100
  for (let i = 0; i < n; i++) {
@@ -110,11 +128,13 @@ function wait(ms) {
110
128
  // Annotate the CommonJS export names for ESM import in node:
111
129
  0 && (module.exports = {
112
130
  compose,
131
+ composeAsync,
113
132
  curry,
114
133
  debounce,
115
134
  delay,
116
135
  getEnv,
117
136
  pipe,
137
+ pipeAsync,
118
138
  throttle,
119
139
  timeout,
120
140
  times,
@@ -35,6 +35,7 @@ __export(lib_exports, {
35
35
  RouteManager: () => RouteManager,
36
36
  camelCaseKeys: () => camelCaseKeys,
37
37
  compose: () => compose,
38
+ composeAsync: () => composeAsync,
38
39
  concatMix: () => concatMix,
39
40
  createMix: () => createMix,
40
41
  createSSE: () => createSSE,
@@ -76,6 +77,7 @@ __export(lib_exports, {
76
77
  parseJSON: () => parseJSON,
77
78
  parseSSE: () => parseSSE,
78
79
  pipe: () => pipe,
80
+ pipeAsync: () => pipeAsync,
79
81
  plural: () => plural,
80
82
  pluralize: () => pluralize,
81
83
  reject: () => reject,
@@ -568,11 +570,27 @@ function compose(...fns) {
568
570
  return fns.reduceRight((acc, fn) => fn(acc), value);
569
571
  };
570
572
  }
573
+ function composeAsync(...fns) {
574
+ return async function(value) {
575
+ return fns.reduceRight(async (acc, fn) => {
576
+ const result = await acc;
577
+ return await fn(result);
578
+ }, value);
579
+ };
580
+ }
571
581
  function pipe(...fns) {
572
582
  return function(value) {
573
583
  return fns.reduce((acc, fn) => fn(acc), value);
574
584
  };
575
585
  }
586
+ function pipeAsync(...fns) {
587
+ return async function(value) {
588
+ return fns.reduce(async (acc, fn) => {
589
+ const result = await acc;
590
+ return await fn(result);
591
+ }, value);
592
+ };
593
+ }
576
594
  function times(fn, n) {
577
595
  const result = [];
578
596
  for (let i = 0; i < n; i++) {
@@ -1065,6 +1083,7 @@ function getSymbolValue(object, description) {
1065
1083
  RouteManager,
1066
1084
  camelCaseKeys,
1067
1085
  compose,
1086
+ composeAsync,
1068
1087
  concatMix,
1069
1088
  createMix,
1070
1089
  createSSE,
@@ -1106,6 +1125,7 @@ function getSymbolValue(object, description) {
1106
1125
  parseJSON,
1107
1126
  parseSSE,
1108
1127
  pipe,
1128
+ pipeAsync,
1109
1129
  plural,
1110
1130
  pluralize,
1111
1131
  reject,
@@ -847,6 +847,94 @@ function mutationObserver(target, options) {
847
847
  };
848
848
  }
849
849
 
850
+ // src/dom/observers/scroll-observer.js
851
+ var defaultOptions2 = {
852
+ threshold: 0,
853
+ // Float between 0 to 1.
854
+ tolerance: 0.1,
855
+ // Float between 0 to 1. Tolerance for event firing
856
+ throttle: 16,
857
+ // Throttle interval in ms (default: ~60fps)
858
+ once: false
859
+ // Only fire threshold callback once
860
+ };
861
+ function scrollObserver(node, options = {}) {
862
+ const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
863
+ const opts = { ...defaultOptions2, ...userOpts };
864
+ const { threshold, tolerance, throttle, once } = opts;
865
+ const prevScrollDirection = null;
866
+ let prevScrollTop = 0;
867
+ let prevScrollPercent = 0;
868
+ let lastThrottleTime = 0;
869
+ let thresholdFired = false;
870
+ let rafId = null;
871
+ const isDocumentScroll = node === document || node === window;
872
+ const scrollElement = isDocumentScroll ? document.documentElement : node;
873
+ let cachedScrollHeight = 0;
874
+ let cachedClientHeight = 0;
875
+ updateCache();
876
+ const cacheObserver = resizeObserver(scrollElement, {
877
+ callback: updateCache
878
+ });
879
+ node.addEventListener("scroll", throttledObserve, { passive: true });
880
+ function updateCache() {
881
+ cachedScrollHeight = scrollElement.scrollHeight;
882
+ cachedClientHeight = scrollElement.clientHeight;
883
+ }
884
+ function throttledObserve() {
885
+ const now = Date.now();
886
+ if (now - lastThrottleTime < throttle) return;
887
+ lastThrottleTime = now;
888
+ rafId = requestAnimationFrame(observe);
889
+ }
890
+ function observe() {
891
+ const scrollTop = scrollElement.scrollTop;
892
+ if (Math.abs(scrollTop - prevScrollTop) < 1) return;
893
+ const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
894
+ const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
895
+ const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
896
+ const thresholdMin = threshold - tolerance / 2;
897
+ const thresholdMax = threshold + tolerance / 2;
898
+ const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
899
+ const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
900
+ const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
901
+ if (hasEnteredThreshold && once) {
902
+ thresholdFired = true;
903
+ }
904
+ const callbackData = {
905
+ scrollTop,
906
+ scrollDirection,
907
+ scrollPercent,
908
+ directionChanged: scrollDirection !== prevScrollDirection,
909
+ hasEnteredThreshold,
910
+ isInThreshold
911
+ };
912
+ if (typeof callback === "function") {
913
+ callback(callbackData);
914
+ }
915
+ if (scrollDirection !== prevScrollDirection) {
916
+ if (scrollDirection === "down" && typeof onScrollDown === "function") {
917
+ onScrollDown(callbackData);
918
+ }
919
+ if (scrollDirection === "up" && typeof onScrollUp === "function") {
920
+ onScrollUp(callbackData);
921
+ }
922
+ }
923
+ if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
924
+ onEnterThreshold(callbackData);
925
+ }
926
+ prevScrollTop = scrollTop;
927
+ prevScrollPercent = scrollPercent;
928
+ }
929
+ return {
930
+ destroy() {
931
+ node.removeEventListener("scroll", throttledObserve);
932
+ if (rafId) cancelAnimationFrame(rafId);
933
+ cacheObserver.destroy();
934
+ }
935
+ };
936
+ }
937
+
850
938
  // src/dom/random-string.js
851
939
  function randomString(length = 10) {
852
940
  const firstLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
@@ -1085,6 +1173,7 @@ export {
1085
1173
  removeListeners,
1086
1174
  resizeObserver,
1087
1175
  sanitize2 as sanitize,
1176
+ scrollObserver,
1088
1177
  scrollToElement,
1089
1178
  sessionStore,
1090
1179
  setCSSValue,