@splendidlabz/utils 1.8.1 → 1.9.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/CHANGELOG.md +13 -0
- package/dist/cjs/dom/events.cjs +0 -36
- package/dist/cjs/dom/index.cjs +90 -36
- package/dist/cjs/dom/observers/index.cjs +92 -2
- package/dist/cjs/dom/observers/scroll-observer.cjs +170 -0
- package/dist/cjs/lib/functions/callback.cjs +31 -0
- package/dist/cjs/lib/functions/functional.cjs +20 -0
- package/dist/cjs/lib/functions/index.cjs +20 -0
- package/dist/cjs/lib/index.cjs +20 -0
- package/dist/esm/dom/events.js +0 -35
- package/dist/esm/dom/index.js +89 -35
- package/dist/esm/dom/observers/index.js +90 -1
- package/dist/esm/dom/observers/scroll-observer.js +144 -0
- package/dist/esm/lib/functions/callback.js +7 -0
- package/dist/esm/lib/functions/functional.js +18 -0
- package/dist/esm/lib/functions/index.js +18 -0
- package/dist/esm/lib/index.js +18 -0
- package/dist/types/dom/events.d.cts +90 -18
- package/dist/types/dom/index.d.cts +2 -1
- package/dist/types/dom/observers/index.d.cts +1 -0
- package/dist/types/dom/observers/scroll-observer.d.cts +27 -0
- package/dist/types/lib/functions/callback.d.cts +8 -0
- package/dist/types/lib/functions/functional.d.cts +71 -5
- package/dist/types/lib/functions/index.d.cts +1 -1
- package/dist/types/lib/index.d.cts +1 -1
- package/package.json +1 -1
- package/src/dom/events.js +44 -50
- package/src/dom/observers/index.js +1 -0
- package/src/dom/observers/resize-observer.js +5 -5
- package/src/dom/observers/scroll-observer.js +132 -0
- package/src/lib/functions/callback.js +8 -0
- package/src/lib/functions/functional.js +82 -0
- package/src/lib/functions/functional.test.js +196 -0
package/dist/cjs/lib/index.cjs
CHANGED
|
@@ -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,
|
package/dist/esm/dom/events.js
CHANGED
|
@@ -24,43 +24,8 @@ function updateEvent(event, updates) {
|
|
|
24
24
|
...updates
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
function createListeners(listeners) {
|
|
28
|
-
listeners = listeners || [];
|
|
29
|
-
listeners.forEach((listener) => {
|
|
30
|
-
const { node, event, handler } = listener;
|
|
31
|
-
node.addEventListener(event, handler);
|
|
32
|
-
});
|
|
33
|
-
return {
|
|
34
|
-
get list() {
|
|
35
|
-
return listeners;
|
|
36
|
-
},
|
|
37
|
-
add({ node, event, handler }) {
|
|
38
|
-
listeners.push({ node, event, handler });
|
|
39
|
-
node.addEventListener(event, handler);
|
|
40
|
-
},
|
|
41
|
-
remove({ node, event, handler }) {
|
|
42
|
-
const index = listeners.findIndex((listener) => {
|
|
43
|
-
if (listener.node === node && listener.event === event && listener.handler === handler) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
return false;
|
|
47
|
-
});
|
|
48
|
-
if (index !== -1) {
|
|
49
|
-
listeners.splice(index, 1);
|
|
50
|
-
node.removeEventListener(event, handler);
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
clear() {
|
|
54
|
-
listeners.forEach(({ node, event, handler }) => {
|
|
55
|
-
listeners = [];
|
|
56
|
-
node.removeEventListener(event, handler);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
27
|
export {
|
|
62
28
|
addListeners,
|
|
63
|
-
createListeners,
|
|
64
29
|
dispatchEvent,
|
|
65
30
|
removeListeners,
|
|
66
31
|
updateEvent
|
package/dist/esm/dom/index.js
CHANGED
|
@@ -134,40 +134,6 @@ function updateEvent(event, updates) {
|
|
|
134
134
|
...updates
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
function createListeners(listeners) {
|
|
138
|
-
listeners = listeners || [];
|
|
139
|
-
listeners.forEach((listener) => {
|
|
140
|
-
const { node, event, handler } = listener;
|
|
141
|
-
node.addEventListener(event, handler);
|
|
142
|
-
});
|
|
143
|
-
return {
|
|
144
|
-
get list() {
|
|
145
|
-
return listeners;
|
|
146
|
-
},
|
|
147
|
-
add({ node, event, handler }) {
|
|
148
|
-
listeners.push({ node, event, handler });
|
|
149
|
-
node.addEventListener(event, handler);
|
|
150
|
-
},
|
|
151
|
-
remove({ node, event, handler }) {
|
|
152
|
-
const index = listeners.findIndex((listener) => {
|
|
153
|
-
if (listener.node === node && listener.event === event && listener.handler === handler) {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
return false;
|
|
157
|
-
});
|
|
158
|
-
if (index !== -1) {
|
|
159
|
-
listeners.splice(index, 1);
|
|
160
|
-
node.removeEventListener(event, handler);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
clear() {
|
|
164
|
-
listeners.forEach(({ node, event, handler }) => {
|
|
165
|
-
listeners = [];
|
|
166
|
-
node.removeEventListener(event, handler);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
137
|
|
|
172
138
|
// src/dom/observers/observer.js
|
|
173
139
|
function useObserverMethodOnTarget(target, observer, method = "observe", options = void 0) {
|
|
@@ -847,6 +813,94 @@ function mutationObserver(target, options) {
|
|
|
847
813
|
};
|
|
848
814
|
}
|
|
849
815
|
|
|
816
|
+
// src/dom/observers/scroll-observer.js
|
|
817
|
+
var defaultOptions2 = {
|
|
818
|
+
threshold: 0,
|
|
819
|
+
// Float between 0 to 1.
|
|
820
|
+
tolerance: 0.1,
|
|
821
|
+
// Float between 0 to 1. Tolerance for event firing
|
|
822
|
+
throttle: 16,
|
|
823
|
+
// Throttle interval in ms (default: ~60fps)
|
|
824
|
+
once: false
|
|
825
|
+
// Only fire threshold callback once
|
|
826
|
+
};
|
|
827
|
+
function scrollObserver(node, options = {}) {
|
|
828
|
+
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
|
|
829
|
+
const opts = { ...defaultOptions2, ...userOpts };
|
|
830
|
+
const { threshold, tolerance, throttle, once } = opts;
|
|
831
|
+
const prevScrollDirection = null;
|
|
832
|
+
let prevScrollTop = 0;
|
|
833
|
+
let prevScrollPercent = 0;
|
|
834
|
+
let lastThrottleTime = 0;
|
|
835
|
+
let thresholdFired = false;
|
|
836
|
+
let rafId = null;
|
|
837
|
+
const isDocumentScroll = node === document || node === window;
|
|
838
|
+
const scrollElement = isDocumentScroll ? document.documentElement : node;
|
|
839
|
+
let cachedScrollHeight = 0;
|
|
840
|
+
let cachedClientHeight = 0;
|
|
841
|
+
updateCache();
|
|
842
|
+
const cacheObserver = resizeObserver(scrollElement, {
|
|
843
|
+
callback: updateCache
|
|
844
|
+
});
|
|
845
|
+
node.addEventListener("scroll", throttledObserve, { passive: true });
|
|
846
|
+
function updateCache() {
|
|
847
|
+
cachedScrollHeight = scrollElement.scrollHeight;
|
|
848
|
+
cachedClientHeight = scrollElement.clientHeight;
|
|
849
|
+
}
|
|
850
|
+
function throttledObserve() {
|
|
851
|
+
const now = Date.now();
|
|
852
|
+
if (now - lastThrottleTime < throttle) return;
|
|
853
|
+
lastThrottleTime = now;
|
|
854
|
+
rafId = requestAnimationFrame(observe);
|
|
855
|
+
}
|
|
856
|
+
function observe() {
|
|
857
|
+
const scrollTop = scrollElement.scrollTop;
|
|
858
|
+
if (Math.abs(scrollTop - prevScrollTop) < 1) return;
|
|
859
|
+
const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
|
|
860
|
+
const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
|
|
861
|
+
const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
|
|
862
|
+
const thresholdMin = threshold - tolerance / 2;
|
|
863
|
+
const thresholdMax = threshold + tolerance / 2;
|
|
864
|
+
const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
|
|
865
|
+
const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
|
|
866
|
+
const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
|
|
867
|
+
if (hasEnteredThreshold && once) {
|
|
868
|
+
thresholdFired = true;
|
|
869
|
+
}
|
|
870
|
+
const callbackData = {
|
|
871
|
+
scrollTop,
|
|
872
|
+
scrollDirection,
|
|
873
|
+
scrollPercent,
|
|
874
|
+
directionChanged: scrollDirection !== prevScrollDirection,
|
|
875
|
+
hasEnteredThreshold,
|
|
876
|
+
isInThreshold
|
|
877
|
+
};
|
|
878
|
+
if (typeof callback === "function") {
|
|
879
|
+
callback(callbackData);
|
|
880
|
+
}
|
|
881
|
+
if (scrollDirection !== prevScrollDirection) {
|
|
882
|
+
if (scrollDirection === "down" && typeof onScrollDown === "function") {
|
|
883
|
+
onScrollDown(callbackData);
|
|
884
|
+
}
|
|
885
|
+
if (scrollDirection === "up" && typeof onScrollUp === "function") {
|
|
886
|
+
onScrollUp(callbackData);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
|
|
890
|
+
onEnterThreshold(callbackData);
|
|
891
|
+
}
|
|
892
|
+
prevScrollTop = scrollTop;
|
|
893
|
+
prevScrollPercent = scrollPercent;
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
destroy() {
|
|
897
|
+
node.removeEventListener("scroll", throttledObserve);
|
|
898
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
899
|
+
cacheObserver.destroy();
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
850
904
|
// src/dom/random-string.js
|
|
851
905
|
function randomString(length = 10) {
|
|
852
906
|
const firstLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
|
|
@@ -1043,7 +1097,6 @@ export {
|
|
|
1043
1097
|
checkForAccessibleName,
|
|
1044
1098
|
cookies,
|
|
1045
1099
|
copyRichText,
|
|
1046
|
-
createListeners,
|
|
1047
1100
|
dispatchEvent,
|
|
1048
1101
|
em,
|
|
1049
1102
|
findScrollContainer,
|
|
@@ -1085,6 +1138,7 @@ export {
|
|
|
1085
1138
|
removeListeners,
|
|
1086
1139
|
resizeObserver,
|
|
1087
1140
|
sanitize2 as sanitize,
|
|
1141
|
+
scrollObserver,
|
|
1088
1142
|
scrollToElement,
|
|
1089
1143
|
sessionStore,
|
|
1090
1144
|
setCSSValue,
|
|
@@ -121,8 +121,97 @@ function resizeObserver(target, options) {
|
|
|
121
121
|
destroy: (_) => observer.disconnect()
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
// src/dom/observers/scroll-observer.js
|
|
126
|
+
var defaultOptions2 = {
|
|
127
|
+
threshold: 0,
|
|
128
|
+
// Float between 0 to 1.
|
|
129
|
+
tolerance: 0.1,
|
|
130
|
+
// Float between 0 to 1. Tolerance for event firing
|
|
131
|
+
throttle: 16,
|
|
132
|
+
// Throttle interval in ms (default: ~60fps)
|
|
133
|
+
once: false
|
|
134
|
+
// Only fire threshold callback once
|
|
135
|
+
};
|
|
136
|
+
function scrollObserver(node, options = {}) {
|
|
137
|
+
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
|
|
138
|
+
const opts = { ...defaultOptions2, ...userOpts };
|
|
139
|
+
const { threshold, tolerance, throttle, once } = opts;
|
|
140
|
+
const prevScrollDirection = null;
|
|
141
|
+
let prevScrollTop = 0;
|
|
142
|
+
let prevScrollPercent = 0;
|
|
143
|
+
let lastThrottleTime = 0;
|
|
144
|
+
let thresholdFired = false;
|
|
145
|
+
let rafId = null;
|
|
146
|
+
const isDocumentScroll = node === document || node === window;
|
|
147
|
+
const scrollElement = isDocumentScroll ? document.documentElement : node;
|
|
148
|
+
let cachedScrollHeight = 0;
|
|
149
|
+
let cachedClientHeight = 0;
|
|
150
|
+
updateCache();
|
|
151
|
+
const cacheObserver = resizeObserver(scrollElement, {
|
|
152
|
+
callback: updateCache
|
|
153
|
+
});
|
|
154
|
+
node.addEventListener("scroll", throttledObserve, { passive: true });
|
|
155
|
+
function updateCache() {
|
|
156
|
+
cachedScrollHeight = scrollElement.scrollHeight;
|
|
157
|
+
cachedClientHeight = scrollElement.clientHeight;
|
|
158
|
+
}
|
|
159
|
+
function throttledObserve() {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
if (now - lastThrottleTime < throttle) return;
|
|
162
|
+
lastThrottleTime = now;
|
|
163
|
+
rafId = requestAnimationFrame(observe);
|
|
164
|
+
}
|
|
165
|
+
function observe() {
|
|
166
|
+
const scrollTop = scrollElement.scrollTop;
|
|
167
|
+
if (Math.abs(scrollTop - prevScrollTop) < 1) return;
|
|
168
|
+
const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
|
|
169
|
+
const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
|
|
170
|
+
const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
|
|
171
|
+
const thresholdMin = threshold - tolerance / 2;
|
|
172
|
+
const thresholdMax = threshold + tolerance / 2;
|
|
173
|
+
const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
|
|
174
|
+
const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
|
|
175
|
+
const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
|
|
176
|
+
if (hasEnteredThreshold && once) {
|
|
177
|
+
thresholdFired = true;
|
|
178
|
+
}
|
|
179
|
+
const callbackData = {
|
|
180
|
+
scrollTop,
|
|
181
|
+
scrollDirection,
|
|
182
|
+
scrollPercent,
|
|
183
|
+
directionChanged: scrollDirection !== prevScrollDirection,
|
|
184
|
+
hasEnteredThreshold,
|
|
185
|
+
isInThreshold
|
|
186
|
+
};
|
|
187
|
+
if (typeof callback === "function") {
|
|
188
|
+
callback(callbackData);
|
|
189
|
+
}
|
|
190
|
+
if (scrollDirection !== prevScrollDirection) {
|
|
191
|
+
if (scrollDirection === "down" && typeof onScrollDown === "function") {
|
|
192
|
+
onScrollDown(callbackData);
|
|
193
|
+
}
|
|
194
|
+
if (scrollDirection === "up" && typeof onScrollUp === "function") {
|
|
195
|
+
onScrollUp(callbackData);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
|
|
199
|
+
onEnterThreshold(callbackData);
|
|
200
|
+
}
|
|
201
|
+
prevScrollTop = scrollTop;
|
|
202
|
+
prevScrollPercent = scrollPercent;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
destroy() {
|
|
206
|
+
node.removeEventListener("scroll", throttledObserve);
|
|
207
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
208
|
+
cacheObserver.destroy();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
124
212
|
export {
|
|
125
213
|
intersectionObserver,
|
|
126
214
|
mutationObserver,
|
|
127
|
-
resizeObserver
|
|
215
|
+
resizeObserver,
|
|
216
|
+
scrollObserver
|
|
128
217
|
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/dom/events.js
|
|
2
|
+
function dispatchEvent(node, eventName, detail, options = {}) {
|
|
3
|
+
node.dispatchEvent(
|
|
4
|
+
new CustomEvent(eventName, {
|
|
5
|
+
...options,
|
|
6
|
+
detail
|
|
7
|
+
})
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/dom/get-element.js
|
|
12
|
+
function getNodeType(node) {
|
|
13
|
+
if (node instanceof Element) return "element";
|
|
14
|
+
if (node instanceof NodeList) return "nodelist";
|
|
15
|
+
if (Array.isArray(node)) return "array";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/dom/observers/observer.js
|
|
19
|
+
function useObserverMethodOnTarget(target, observer, method = "observe", options = void 0) {
|
|
20
|
+
const targetType = getNodeType(target);
|
|
21
|
+
if (targetType === "element") observer[method](target, options);
|
|
22
|
+
if (targetType === "nodelist") {
|
|
23
|
+
const elements = Array.from(target);
|
|
24
|
+
elements.forEach((element) => observer[method](element, options));
|
|
25
|
+
}
|
|
26
|
+
if (targetType === "array") {
|
|
27
|
+
target.forEach((element) => observer[method](element, options));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/dom/observers/resize-observer.js
|
|
32
|
+
function resizeObserver(target, options) {
|
|
33
|
+
const { callback, ...opts } = options;
|
|
34
|
+
const observer = new ResizeObserver(observerFn);
|
|
35
|
+
if (target === window) target = document.body;
|
|
36
|
+
useObserverMethodOnTarget(target, observer, "observe", opts);
|
|
37
|
+
function observerFn(entries) {
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (callback) callback({ entry, entries, observer });
|
|
40
|
+
else dispatchEvent(target, "resize-obs", { entry, entries, observer });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
observe(target2) {
|
|
45
|
+
useObserverMethodOnTarget(target2, observer, "observe", options);
|
|
46
|
+
},
|
|
47
|
+
unobserve(target2) {
|
|
48
|
+
useObserverMethodOnTarget(target2, observer, "unobserve");
|
|
49
|
+
},
|
|
50
|
+
disconnect: (_) => observer.disconnect(),
|
|
51
|
+
destroy: (_) => observer.disconnect()
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/dom/observers/scroll-observer.js
|
|
56
|
+
var defaultOptions = {
|
|
57
|
+
threshold: 0,
|
|
58
|
+
// Float between 0 to 1.
|
|
59
|
+
tolerance: 0.1,
|
|
60
|
+
// Float between 0 to 1. Tolerance for event firing
|
|
61
|
+
throttle: 16,
|
|
62
|
+
// Throttle interval in ms (default: ~60fps)
|
|
63
|
+
once: false
|
|
64
|
+
// Only fire threshold callback once
|
|
65
|
+
};
|
|
66
|
+
function scrollObserver(node, options = {}) {
|
|
67
|
+
const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
|
|
68
|
+
const opts = { ...defaultOptions, ...userOpts };
|
|
69
|
+
const { threshold, tolerance, throttle, once } = opts;
|
|
70
|
+
const prevScrollDirection = null;
|
|
71
|
+
let prevScrollTop = 0;
|
|
72
|
+
let prevScrollPercent = 0;
|
|
73
|
+
let lastThrottleTime = 0;
|
|
74
|
+
let thresholdFired = false;
|
|
75
|
+
let rafId = null;
|
|
76
|
+
const isDocumentScroll = node === document || node === window;
|
|
77
|
+
const scrollElement = isDocumentScroll ? document.documentElement : node;
|
|
78
|
+
let cachedScrollHeight = 0;
|
|
79
|
+
let cachedClientHeight = 0;
|
|
80
|
+
updateCache();
|
|
81
|
+
const cacheObserver = resizeObserver(scrollElement, {
|
|
82
|
+
callback: updateCache
|
|
83
|
+
});
|
|
84
|
+
node.addEventListener("scroll", throttledObserve, { passive: true });
|
|
85
|
+
function updateCache() {
|
|
86
|
+
cachedScrollHeight = scrollElement.scrollHeight;
|
|
87
|
+
cachedClientHeight = scrollElement.clientHeight;
|
|
88
|
+
}
|
|
89
|
+
function throttledObserve() {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
if (now - lastThrottleTime < throttle) return;
|
|
92
|
+
lastThrottleTime = now;
|
|
93
|
+
rafId = requestAnimationFrame(observe);
|
|
94
|
+
}
|
|
95
|
+
function observe() {
|
|
96
|
+
const scrollTop = scrollElement.scrollTop;
|
|
97
|
+
if (Math.abs(scrollTop - prevScrollTop) < 1) return;
|
|
98
|
+
const scrollDirection = scrollTop > prevScrollTop ? "down" : "up";
|
|
99
|
+
const maxScroll = Math.max(1, cachedScrollHeight - cachedClientHeight);
|
|
100
|
+
const scrollPercent = Math.min(1, Math.max(0, scrollTop / maxScroll));
|
|
101
|
+
const thresholdMin = threshold - tolerance / 2;
|
|
102
|
+
const thresholdMax = threshold + tolerance / 2;
|
|
103
|
+
const wasInThreshold = prevScrollPercent >= thresholdMin && prevScrollPercent <= thresholdMax;
|
|
104
|
+
const isInThreshold = scrollPercent >= thresholdMin && scrollPercent <= thresholdMax;
|
|
105
|
+
const hasEnteredThreshold = !wasInThreshold && isInThreshold && (!once || !thresholdFired);
|
|
106
|
+
if (hasEnteredThreshold && once) {
|
|
107
|
+
thresholdFired = true;
|
|
108
|
+
}
|
|
109
|
+
const callbackData = {
|
|
110
|
+
scrollTop,
|
|
111
|
+
scrollDirection,
|
|
112
|
+
scrollPercent,
|
|
113
|
+
directionChanged: scrollDirection !== prevScrollDirection,
|
|
114
|
+
hasEnteredThreshold,
|
|
115
|
+
isInThreshold
|
|
116
|
+
};
|
|
117
|
+
if (typeof callback === "function") {
|
|
118
|
+
callback(callbackData);
|
|
119
|
+
}
|
|
120
|
+
if (scrollDirection !== prevScrollDirection) {
|
|
121
|
+
if (scrollDirection === "down" && typeof onScrollDown === "function") {
|
|
122
|
+
onScrollDown(callbackData);
|
|
123
|
+
}
|
|
124
|
+
if (scrollDirection === "up" && typeof onScrollUp === "function") {
|
|
125
|
+
onScrollUp(callbackData);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (hasEnteredThreshold && typeof onEnterThreshold === "function") {
|
|
129
|
+
onEnterThreshold(callbackData);
|
|
130
|
+
}
|
|
131
|
+
prevScrollTop = scrollTop;
|
|
132
|
+
prevScrollPercent = scrollPercent;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
destroy() {
|
|
136
|
+
node.removeEventListener("scroll", throttledObserve);
|
|
137
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
138
|
+
cacheObserver.destroy();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export {
|
|
143
|
+
scrollObserver
|
|
144
|
+
};
|
|
@@ -15,11 +15,27 @@ function compose(...fns) {
|
|
|
15
15
|
return fns.reduceRight((acc, fn) => fn(acc), value);
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
function composeAsync(...fns) {
|
|
19
|
+
return async function(value) {
|
|
20
|
+
return fns.reduceRight(async (acc, fn) => {
|
|
21
|
+
const result = await acc;
|
|
22
|
+
return await fn(result);
|
|
23
|
+
}, value);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
18
26
|
function pipe(...fns) {
|
|
19
27
|
return function(value) {
|
|
20
28
|
return fns.reduce((acc, fn) => fn(acc), value);
|
|
21
29
|
};
|
|
22
30
|
}
|
|
31
|
+
function pipeAsync(...fns) {
|
|
32
|
+
return async function(value) {
|
|
33
|
+
return fns.reduce(async (acc, fn) => {
|
|
34
|
+
const result = await acc;
|
|
35
|
+
return await fn(result);
|
|
36
|
+
}, value);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
23
39
|
function times(fn, n) {
|
|
24
40
|
const result = [];
|
|
25
41
|
for (let i = 0; i < n; i++) {
|
|
@@ -29,7 +45,9 @@ function times(fn, n) {
|
|
|
29
45
|
}
|
|
30
46
|
export {
|
|
31
47
|
compose,
|
|
48
|
+
composeAsync,
|
|
32
49
|
curry,
|
|
33
50
|
pipe,
|
|
51
|
+
pipeAsync,
|
|
34
52
|
times
|
|
35
53
|
};
|
|
@@ -38,11 +38,27 @@ function compose(...fns) {
|
|
|
38
38
|
return fns.reduceRight((acc, fn) => fn(acc), value);
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
function composeAsync(...fns) {
|
|
42
|
+
return async function(value) {
|
|
43
|
+
return fns.reduceRight(async (acc, fn) => {
|
|
44
|
+
const result = await acc;
|
|
45
|
+
return await fn(result);
|
|
46
|
+
}, value);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
41
49
|
function pipe(...fns) {
|
|
42
50
|
return function(value) {
|
|
43
51
|
return fns.reduce((acc, fn) => fn(acc), value);
|
|
44
52
|
};
|
|
45
53
|
}
|
|
54
|
+
function pipeAsync(...fns) {
|
|
55
|
+
return async function(value) {
|
|
56
|
+
return fns.reduce(async (acc, fn) => {
|
|
57
|
+
const result = await acc;
|
|
58
|
+
return await fn(result);
|
|
59
|
+
}, value);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
46
62
|
function times(fn, n) {
|
|
47
63
|
const result = [];
|
|
48
64
|
for (let i = 0; i < n; i++) {
|
|
@@ -75,11 +91,13 @@ function wait(ms) {
|
|
|
75
91
|
}
|
|
76
92
|
export {
|
|
77
93
|
compose,
|
|
94
|
+
composeAsync,
|
|
78
95
|
curry,
|
|
79
96
|
debounce,
|
|
80
97
|
delay,
|
|
81
98
|
getEnv,
|
|
82
99
|
pipe,
|
|
100
|
+
pipeAsync,
|
|
83
101
|
throttle,
|
|
84
102
|
timeout,
|
|
85
103
|
times,
|
package/dist/esm/lib/index.js
CHANGED
|
@@ -450,11 +450,27 @@ function compose(...fns) {
|
|
|
450
450
|
return fns.reduceRight((acc, fn) => fn(acc), value);
|
|
451
451
|
};
|
|
452
452
|
}
|
|
453
|
+
function composeAsync(...fns) {
|
|
454
|
+
return async function(value) {
|
|
455
|
+
return fns.reduceRight(async (acc, fn) => {
|
|
456
|
+
const result = await acc;
|
|
457
|
+
return await fn(result);
|
|
458
|
+
}, value);
|
|
459
|
+
};
|
|
460
|
+
}
|
|
453
461
|
function pipe(...fns) {
|
|
454
462
|
return function(value) {
|
|
455
463
|
return fns.reduce((acc, fn) => fn(acc), value);
|
|
456
464
|
};
|
|
457
465
|
}
|
|
466
|
+
function pipeAsync(...fns) {
|
|
467
|
+
return async function(value) {
|
|
468
|
+
return fns.reduce(async (acc, fn) => {
|
|
469
|
+
const result = await acc;
|
|
470
|
+
return await fn(result);
|
|
471
|
+
}, value);
|
|
472
|
+
};
|
|
473
|
+
}
|
|
458
474
|
function times(fn, n) {
|
|
459
475
|
const result = [];
|
|
460
476
|
for (let i = 0; i < n; i++) {
|
|
@@ -946,6 +962,7 @@ export {
|
|
|
946
962
|
RouteManager,
|
|
947
963
|
camelCaseKeys,
|
|
948
964
|
compose,
|
|
965
|
+
composeAsync,
|
|
949
966
|
concatMix,
|
|
950
967
|
createMix,
|
|
951
968
|
createSSE,
|
|
@@ -987,6 +1004,7 @@ export {
|
|
|
987
1004
|
parseJSON,
|
|
988
1005
|
parseSSE,
|
|
989
1006
|
pipe,
|
|
1007
|
+
pipeAsync,
|
|
990
1008
|
plural,
|
|
991
1009
|
pluralize,
|
|
992
1010
|
reject,
|