domet 1.0.1 → 1.0.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/README.md +5 -3
- package/dist/{useDomet.d.ts → cjs/index.d.ts} +14 -12
- package/dist/{useDomet.js → cjs/index.js} +167 -154
- package/dist/{useScrowl.d.ts → es/index.d.mts} +14 -12
- package/dist/es/index.d.ts +62 -0
- package/dist/{useScrowl.js → es/index.js} +172 -156
- package/dist/es/index.mjs +504 -0
- package/package.json +20 -13
- package/LICENSE +0 -21
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -1
- package/dist/useDomet.d.ts.map +0 -1
- package/dist/useScrowl.d.ts.map +0 -1
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { useMemo, useState, useRef, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_VISIBILITY_THRESHOLD = 0.6;
|
|
4
|
+
const DEFAULT_HYSTERESIS_MARGIN = 150;
|
|
5
|
+
const SCROLL_IDLE_MS = 100;
|
|
6
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
7
|
+
function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
8
|
+
const { offset = 0, offsetRatio = 0.08, debounceMs = 10, visibilityThreshold = DEFAULT_VISIBILITY_THRESHOLD, hysteresisMargin = DEFAULT_HYSTERESIS_MARGIN, behavior = "auto", onActiveChange, onSectionEnter, onSectionLeave, onScrollStart, onScrollEnd } = options;
|
|
9
|
+
JSON.stringify(sectionIds);
|
|
10
|
+
const stableSectionIds = useMemo(()=>sectionIds, [
|
|
11
|
+
sectionIds
|
|
12
|
+
]);
|
|
13
|
+
const sectionIndexMap = useMemo(()=>{
|
|
14
|
+
const map = new Map();
|
|
15
|
+
for(let i = 0; i < stableSectionIds.length; i++){
|
|
16
|
+
map.set(stableSectionIds[i], i);
|
|
17
|
+
}
|
|
18
|
+
return map;
|
|
19
|
+
}, [
|
|
20
|
+
stableSectionIds
|
|
21
|
+
]);
|
|
22
|
+
const [activeId, setActiveId] = useState(stableSectionIds[0] || null);
|
|
23
|
+
const [scroll, setScroll] = useState({
|
|
24
|
+
y: 0,
|
|
25
|
+
progress: 0,
|
|
26
|
+
direction: null,
|
|
27
|
+
velocity: 0,
|
|
28
|
+
isScrolling: false,
|
|
29
|
+
maxScroll: 0,
|
|
30
|
+
viewportHeight: 0,
|
|
31
|
+
offset: 0
|
|
32
|
+
});
|
|
33
|
+
const [sections, setSections] = useState({});
|
|
34
|
+
const [containerElement, setContainerElement] = useState(null);
|
|
35
|
+
const refs = useRef({});
|
|
36
|
+
const refCallbacks = useRef({});
|
|
37
|
+
const activeIdRef = useRef(stableSectionIds[0] || null);
|
|
38
|
+
const lastScrollY = useRef(0);
|
|
39
|
+
const lastScrollTime = useRef(Date.now());
|
|
40
|
+
const rafId = useRef(null);
|
|
41
|
+
const isThrottled = useRef(false);
|
|
42
|
+
const throttleTimeoutId = useRef(null);
|
|
43
|
+
const hasPendingScroll = useRef(false);
|
|
44
|
+
const isProgrammaticScrolling = useRef(false);
|
|
45
|
+
const programmaticScrollTimeoutId = useRef(null);
|
|
46
|
+
const isScrollingRef = useRef(false);
|
|
47
|
+
const scrollIdleTimeoutRef = useRef(null);
|
|
48
|
+
const prevSectionsInViewport = useRef(new Set());
|
|
49
|
+
const recalculateRef = useRef(()=>{});
|
|
50
|
+
const scrollCleanupRef = useRef(null);
|
|
51
|
+
const callbackRefs = useRef({
|
|
52
|
+
onActiveChange,
|
|
53
|
+
onSectionEnter,
|
|
54
|
+
onSectionLeave,
|
|
55
|
+
onScrollStart,
|
|
56
|
+
onScrollEnd
|
|
57
|
+
});
|
|
58
|
+
callbackRefs.current = {
|
|
59
|
+
onActiveChange,
|
|
60
|
+
onSectionEnter,
|
|
61
|
+
onSectionLeave,
|
|
62
|
+
onScrollStart,
|
|
63
|
+
onScrollEnd
|
|
64
|
+
};
|
|
65
|
+
const getEffectiveOffset = useCallback(()=>{
|
|
66
|
+
return offset;
|
|
67
|
+
}, [
|
|
68
|
+
offset
|
|
69
|
+
]);
|
|
70
|
+
const getScrollBehavior = useCallback(()=>{
|
|
71
|
+
if (behavior === "auto") {
|
|
72
|
+
if (typeof window === "undefined") return "instant";
|
|
73
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
74
|
+
return prefersReducedMotion ? "instant" : "smooth";
|
|
75
|
+
}
|
|
76
|
+
return behavior;
|
|
77
|
+
}, [
|
|
78
|
+
behavior
|
|
79
|
+
]);
|
|
80
|
+
useIsomorphicLayoutEffect(()=>{
|
|
81
|
+
var _ref;
|
|
82
|
+
const nextContainer = (_ref = containerRef == null ? void 0 : containerRef.current) != null ? _ref : null;
|
|
83
|
+
if (nextContainer !== containerElement) {
|
|
84
|
+
setContainerElement(nextContainer);
|
|
85
|
+
}
|
|
86
|
+
}, [
|
|
87
|
+
containerRef,
|
|
88
|
+
containerElement
|
|
89
|
+
]);
|
|
90
|
+
const registerRef = useCallback((id)=>{
|
|
91
|
+
const existing = refCallbacks.current[id];
|
|
92
|
+
if (existing) return existing;
|
|
93
|
+
const callback = (el)=>{
|
|
94
|
+
if (el) {
|
|
95
|
+
refs.current[id] = el;
|
|
96
|
+
} else {
|
|
97
|
+
delete refs.current[id];
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
refCallbacks.current[id] = callback;
|
|
101
|
+
return callback;
|
|
102
|
+
}, []);
|
|
103
|
+
const scrollToSection = useCallback((id)=>{
|
|
104
|
+
if (!stableSectionIds.includes(id)) {
|
|
105
|
+
if (process.env.NODE_ENV !== "production") {
|
|
106
|
+
console.warn(`[domet] scrollToSection: id "${id}" not in sectionIds`);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const element = refs.current[id];
|
|
111
|
+
if (!element) return;
|
|
112
|
+
if (programmaticScrollTimeoutId.current) {
|
|
113
|
+
clearTimeout(programmaticScrollTimeoutId.current);
|
|
114
|
+
}
|
|
115
|
+
scrollCleanupRef.current == null ? void 0 : scrollCleanupRef.current.call(scrollCleanupRef);
|
|
116
|
+
isProgrammaticScrolling.current = true;
|
|
117
|
+
activeIdRef.current = id;
|
|
118
|
+
setActiveId(id);
|
|
119
|
+
const container = containerElement;
|
|
120
|
+
const elementRect = element.getBoundingClientRect();
|
|
121
|
+
const effectiveOffset = getEffectiveOffset() + 10;
|
|
122
|
+
const scrollTarget = container || window;
|
|
123
|
+
const unlockScroll = ()=>{
|
|
124
|
+
isProgrammaticScrolling.current = false;
|
|
125
|
+
if (programmaticScrollTimeoutId.current) {
|
|
126
|
+
clearTimeout(programmaticScrollTimeoutId.current);
|
|
127
|
+
programmaticScrollTimeoutId.current = null;
|
|
128
|
+
}
|
|
129
|
+
requestAnimationFrame(()=>{
|
|
130
|
+
recalculateRef.current();
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
let debounceTimer = null;
|
|
134
|
+
let isUnlocked = false;
|
|
135
|
+
const cleanup = ()=>{
|
|
136
|
+
if (debounceTimer) {
|
|
137
|
+
clearTimeout(debounceTimer);
|
|
138
|
+
debounceTimer = null;
|
|
139
|
+
}
|
|
140
|
+
scrollTarget.removeEventListener("scroll", handleScrollActivity);
|
|
141
|
+
if ("onscrollend" in scrollTarget) {
|
|
142
|
+
scrollTarget.removeEventListener("scrollend", handleScrollEnd);
|
|
143
|
+
}
|
|
144
|
+
scrollCleanupRef.current = null;
|
|
145
|
+
};
|
|
146
|
+
const doUnlock = ()=>{
|
|
147
|
+
if (isUnlocked) return;
|
|
148
|
+
isUnlocked = true;
|
|
149
|
+
cleanup();
|
|
150
|
+
unlockScroll();
|
|
151
|
+
};
|
|
152
|
+
const resetDebounce = ()=>{
|
|
153
|
+
if (debounceTimer) {
|
|
154
|
+
clearTimeout(debounceTimer);
|
|
155
|
+
}
|
|
156
|
+
debounceTimer = setTimeout(doUnlock, SCROLL_IDLE_MS);
|
|
157
|
+
};
|
|
158
|
+
const handleScrollActivity = ()=>{
|
|
159
|
+
resetDebounce();
|
|
160
|
+
};
|
|
161
|
+
const handleScrollEnd = ()=>{
|
|
162
|
+
doUnlock();
|
|
163
|
+
};
|
|
164
|
+
scrollTarget.addEventListener("scroll", handleScrollActivity, {
|
|
165
|
+
passive: true
|
|
166
|
+
});
|
|
167
|
+
if ("onscrollend" in scrollTarget) {
|
|
168
|
+
scrollTarget.addEventListener("scrollend", handleScrollEnd, {
|
|
169
|
+
once: true
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
scrollCleanupRef.current = cleanup;
|
|
173
|
+
const scrollBehavior = getScrollBehavior();
|
|
174
|
+
if (container) {
|
|
175
|
+
const containerRect = container.getBoundingClientRect();
|
|
176
|
+
const relativeTop = elementRect.top - containerRect.top + container.scrollTop;
|
|
177
|
+
container.scrollTo({
|
|
178
|
+
top: relativeTop - effectiveOffset,
|
|
179
|
+
behavior: scrollBehavior
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
const absoluteTop = elementRect.top + window.scrollY;
|
|
183
|
+
window.scrollTo({
|
|
184
|
+
top: absoluteTop - effectiveOffset,
|
|
185
|
+
behavior: scrollBehavior
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (scrollBehavior === "instant") {
|
|
189
|
+
doUnlock();
|
|
190
|
+
} else {
|
|
191
|
+
resetDebounce();
|
|
192
|
+
}
|
|
193
|
+
}, [
|
|
194
|
+
stableSectionIds,
|
|
195
|
+
containerElement,
|
|
196
|
+
getEffectiveOffset,
|
|
197
|
+
getScrollBehavior
|
|
198
|
+
]);
|
|
199
|
+
const sectionProps = useCallback((id)=>({
|
|
200
|
+
id,
|
|
201
|
+
ref: registerRef(id),
|
|
202
|
+
"data-domet": id
|
|
203
|
+
}), [
|
|
204
|
+
registerRef
|
|
205
|
+
]);
|
|
206
|
+
const navProps = useCallback((id)=>({
|
|
207
|
+
onClick: ()=>scrollToSection(id),
|
|
208
|
+
"aria-current": activeId === id ? "page" : undefined,
|
|
209
|
+
"data-active": activeId === id
|
|
210
|
+
}), [
|
|
211
|
+
activeId,
|
|
212
|
+
scrollToSection
|
|
213
|
+
]);
|
|
214
|
+
useEffect(()=>{
|
|
215
|
+
var _stableSectionIds_;
|
|
216
|
+
const idsSet = new Set(stableSectionIds);
|
|
217
|
+
for (const id of Object.keys(refs.current)){
|
|
218
|
+
if (!idsSet.has(id)) {
|
|
219
|
+
delete refs.current[id];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (const id of Object.keys(refCallbacks.current)){
|
|
223
|
+
if (!idsSet.has(id)) {
|
|
224
|
+
delete refCallbacks.current[id];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const currentActive = activeIdRef.current;
|
|
228
|
+
const nextActive = currentActive && idsSet.has(currentActive) ? currentActive : (_stableSectionIds_ = stableSectionIds[0]) != null ? _stableSectionIds_ : null;
|
|
229
|
+
if (nextActive !== currentActive) {
|
|
230
|
+
activeIdRef.current = nextActive;
|
|
231
|
+
}
|
|
232
|
+
setActiveId((prev)=>prev !== nextActive ? nextActive : prev);
|
|
233
|
+
}, [
|
|
234
|
+
stableSectionIds
|
|
235
|
+
]);
|
|
236
|
+
const getSectionBounds = useCallback(()=>{
|
|
237
|
+
const container = containerElement;
|
|
238
|
+
const scrollTop = container ? container.scrollTop : window.scrollY;
|
|
239
|
+
const containerTop = container ? container.getBoundingClientRect().top : 0;
|
|
240
|
+
return stableSectionIds.map((id)=>{
|
|
241
|
+
const el = refs.current[id];
|
|
242
|
+
if (!el) return null;
|
|
243
|
+
const rect = el.getBoundingClientRect();
|
|
244
|
+
const relativeTop = container ? rect.top - containerTop + scrollTop : rect.top + window.scrollY;
|
|
245
|
+
return {
|
|
246
|
+
id,
|
|
247
|
+
top: relativeTop,
|
|
248
|
+
bottom: relativeTop + rect.height,
|
|
249
|
+
height: rect.height
|
|
250
|
+
};
|
|
251
|
+
}).filter((bounds)=>bounds !== null);
|
|
252
|
+
}, [
|
|
253
|
+
stableSectionIds,
|
|
254
|
+
containerElement
|
|
255
|
+
]);
|
|
256
|
+
const calculateActiveSection = useCallback(()=>{
|
|
257
|
+
if (isProgrammaticScrolling.current) return;
|
|
258
|
+
const container = containerElement;
|
|
259
|
+
const currentActiveId = activeIdRef.current;
|
|
260
|
+
const now = Date.now();
|
|
261
|
+
const scrollY = container ? container.scrollTop : window.scrollY;
|
|
262
|
+
const viewportHeight = container ? container.clientHeight : window.innerHeight;
|
|
263
|
+
const scrollHeight = container ? container.scrollHeight : document.documentElement.scrollHeight;
|
|
264
|
+
const maxScroll = Math.max(0, scrollHeight - viewportHeight);
|
|
265
|
+
const scrollProgress = maxScroll > 0 ? scrollY / maxScroll : 0;
|
|
266
|
+
const scrollDirection = scrollY === lastScrollY.current ? null : scrollY > lastScrollY.current ? "down" : "up";
|
|
267
|
+
const deltaTime = now - lastScrollTime.current;
|
|
268
|
+
const deltaY = scrollY - lastScrollY.current;
|
|
269
|
+
const velocity = deltaTime > 0 ? Math.abs(deltaY) / deltaTime : 0;
|
|
270
|
+
lastScrollY.current = scrollY;
|
|
271
|
+
lastScrollTime.current = now;
|
|
272
|
+
const sectionBounds = getSectionBounds();
|
|
273
|
+
if (sectionBounds.length === 0) return;
|
|
274
|
+
const baseOffset = getEffectiveOffset();
|
|
275
|
+
const effectiveOffset = Math.max(baseOffset, viewportHeight * offsetRatio);
|
|
276
|
+
const triggerLine = scrollY + effectiveOffset;
|
|
277
|
+
const viewportTop = scrollY;
|
|
278
|
+
const viewportBottom = scrollY + viewportHeight;
|
|
279
|
+
const scores = sectionBounds.map((section)=>{
|
|
280
|
+
var _sectionIndexMap_get;
|
|
281
|
+
const visibleTop = Math.max(section.top, viewportTop);
|
|
282
|
+
const visibleBottom = Math.min(section.bottom, viewportBottom);
|
|
283
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
284
|
+
const visibilityRatio = section.height > 0 ? visibleHeight / section.height : 0;
|
|
285
|
+
const visibleInViewportRatio = viewportHeight > 0 ? visibleHeight / viewportHeight : 0;
|
|
286
|
+
const isInViewport = section.bottom > viewportTop && section.top < viewportBottom;
|
|
287
|
+
const sectionProgress = (()=>{
|
|
288
|
+
if (section.height === 0) return 0;
|
|
289
|
+
const entryPoint = viewportBottom;
|
|
290
|
+
const totalTravel = viewportHeight + section.height;
|
|
291
|
+
const traveled = entryPoint - section.top;
|
|
292
|
+
return Math.max(0, Math.min(1, traveled / totalTravel));
|
|
293
|
+
})();
|
|
294
|
+
let score = 0;
|
|
295
|
+
if (visibilityRatio >= visibilityThreshold) {
|
|
296
|
+
score += 1000 + visibilityRatio * 500;
|
|
297
|
+
} else if (isInViewport) {
|
|
298
|
+
score += visibleInViewportRatio * 800;
|
|
299
|
+
}
|
|
300
|
+
const sectionIndex = (_sectionIndexMap_get = sectionIndexMap.get(section.id)) != null ? _sectionIndexMap_get : 0;
|
|
301
|
+
if (scrollDirection && isInViewport && section.top <= triggerLine && section.bottom > triggerLine) {
|
|
302
|
+
score += 200;
|
|
303
|
+
}
|
|
304
|
+
score -= sectionIndex * 0.1;
|
|
305
|
+
return {
|
|
306
|
+
id: section.id,
|
|
307
|
+
score,
|
|
308
|
+
visibilityRatio,
|
|
309
|
+
isInViewport,
|
|
310
|
+
bounds: section,
|
|
311
|
+
progress: sectionProgress
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
const hasScroll = maxScroll > 10;
|
|
315
|
+
const isAtBottom = hasScroll && scrollY + viewportHeight >= scrollHeight - 5;
|
|
316
|
+
const isAtTop = hasScroll && scrollY <= 5;
|
|
317
|
+
let newActiveId = null;
|
|
318
|
+
if (isAtBottom && stableSectionIds.length > 0) {
|
|
319
|
+
newActiveId = stableSectionIds[stableSectionIds.length - 1];
|
|
320
|
+
} else if (isAtTop && stableSectionIds.length > 0) {
|
|
321
|
+
newActiveId = stableSectionIds[0];
|
|
322
|
+
} else {
|
|
323
|
+
const visibleScores = scores.filter((s)=>s.isInViewport);
|
|
324
|
+
const candidates = visibleScores.length > 0 ? visibleScores : scores;
|
|
325
|
+
candidates.sort((a, b)=>b.score - a.score);
|
|
326
|
+
if (candidates.length > 0) {
|
|
327
|
+
const bestCandidate = candidates[0];
|
|
328
|
+
const currentScore = scores.find((s)=>s.id === currentActiveId);
|
|
329
|
+
const shouldSwitch = !currentScore || !currentScore.isInViewport || bestCandidate.score > currentScore.score + hysteresisMargin || bestCandidate.id === currentActiveId;
|
|
330
|
+
newActiveId = shouldSwitch ? bestCandidate.id : currentActiveId;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (newActiveId !== currentActiveId) {
|
|
334
|
+
activeIdRef.current = newActiveId;
|
|
335
|
+
setActiveId(newActiveId);
|
|
336
|
+
callbackRefs.current.onActiveChange == null ? void 0 : callbackRefs.current.onActiveChange.call(callbackRefs.current, newActiveId, currentActiveId);
|
|
337
|
+
}
|
|
338
|
+
const currentInViewport = new Set(scores.filter((s)=>s.isInViewport).map((s)=>s.id));
|
|
339
|
+
const prevInViewport = prevSectionsInViewport.current;
|
|
340
|
+
for (const id of currentInViewport){
|
|
341
|
+
if (!prevInViewport.has(id)) {
|
|
342
|
+
callbackRefs.current.onSectionEnter == null ? void 0 : callbackRefs.current.onSectionEnter.call(callbackRefs.current, id);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
for (const id of prevInViewport){
|
|
346
|
+
if (!currentInViewport.has(id)) {
|
|
347
|
+
callbackRefs.current.onSectionLeave == null ? void 0 : callbackRefs.current.onSectionLeave.call(callbackRefs.current, id);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
prevSectionsInViewport.current = currentInViewport;
|
|
351
|
+
const newScrollState = {
|
|
352
|
+
y: scrollY,
|
|
353
|
+
progress: Math.max(0, Math.min(1, scrollProgress)),
|
|
354
|
+
direction: scrollDirection,
|
|
355
|
+
velocity,
|
|
356
|
+
isScrolling: isScrollingRef.current,
|
|
357
|
+
maxScroll,
|
|
358
|
+
viewportHeight,
|
|
359
|
+
offset: effectiveOffset
|
|
360
|
+
};
|
|
361
|
+
const newSections = {};
|
|
362
|
+
for (const s of scores){
|
|
363
|
+
newSections[s.id] = {
|
|
364
|
+
bounds: {
|
|
365
|
+
top: s.bounds.top,
|
|
366
|
+
bottom: s.bounds.bottom,
|
|
367
|
+
height: s.bounds.height
|
|
368
|
+
},
|
|
369
|
+
visibility: Math.round(s.visibilityRatio * 100) / 100,
|
|
370
|
+
progress: Math.round(s.progress * 100) / 100,
|
|
371
|
+
isInViewport: s.isInViewport,
|
|
372
|
+
isActive: s.id === newActiveId
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
setScroll(newScrollState);
|
|
376
|
+
setSections(newSections);
|
|
377
|
+
}, [
|
|
378
|
+
stableSectionIds,
|
|
379
|
+
sectionIndexMap,
|
|
380
|
+
getEffectiveOffset,
|
|
381
|
+
offsetRatio,
|
|
382
|
+
visibilityThreshold,
|
|
383
|
+
hysteresisMargin,
|
|
384
|
+
getSectionBounds,
|
|
385
|
+
containerElement
|
|
386
|
+
]);
|
|
387
|
+
recalculateRef.current = calculateActiveSection;
|
|
388
|
+
useEffect(()=>{
|
|
389
|
+
const container = containerElement;
|
|
390
|
+
const scrollTarget = container || window;
|
|
391
|
+
const scheduleCalculate = ()=>{
|
|
392
|
+
if (rafId.current) {
|
|
393
|
+
cancelAnimationFrame(rafId.current);
|
|
394
|
+
}
|
|
395
|
+
rafId.current = requestAnimationFrame(()=>{
|
|
396
|
+
rafId.current = null;
|
|
397
|
+
calculateActiveSection();
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
const handleScrollEnd = ()=>{
|
|
401
|
+
isScrollingRef.current = false;
|
|
402
|
+
setScroll((prev)=>({
|
|
403
|
+
...prev,
|
|
404
|
+
isScrolling: false
|
|
405
|
+
}));
|
|
406
|
+
callbackRefs.current.onScrollEnd == null ? void 0 : callbackRefs.current.onScrollEnd.call(callbackRefs.current);
|
|
407
|
+
};
|
|
408
|
+
const handleScroll = ()=>{
|
|
409
|
+
if (!isScrollingRef.current) {
|
|
410
|
+
isScrollingRef.current = true;
|
|
411
|
+
setScroll((prev)=>({
|
|
412
|
+
...prev,
|
|
413
|
+
isScrolling: true
|
|
414
|
+
}));
|
|
415
|
+
callbackRefs.current.onScrollStart == null ? void 0 : callbackRefs.current.onScrollStart.call(callbackRefs.current);
|
|
416
|
+
}
|
|
417
|
+
if (scrollIdleTimeoutRef.current) {
|
|
418
|
+
clearTimeout(scrollIdleTimeoutRef.current);
|
|
419
|
+
}
|
|
420
|
+
scrollIdleTimeoutRef.current = setTimeout(handleScrollEnd, SCROLL_IDLE_MS);
|
|
421
|
+
if (isThrottled.current) {
|
|
422
|
+
hasPendingScroll.current = true;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
isThrottled.current = true;
|
|
426
|
+
hasPendingScroll.current = false;
|
|
427
|
+
if (throttleTimeoutId.current) {
|
|
428
|
+
clearTimeout(throttleTimeoutId.current);
|
|
429
|
+
}
|
|
430
|
+
scheduleCalculate();
|
|
431
|
+
throttleTimeoutId.current = setTimeout(()=>{
|
|
432
|
+
isThrottled.current = false;
|
|
433
|
+
throttleTimeoutId.current = null;
|
|
434
|
+
if (hasPendingScroll.current) {
|
|
435
|
+
hasPendingScroll.current = false;
|
|
436
|
+
handleScroll();
|
|
437
|
+
}
|
|
438
|
+
}, debounceMs);
|
|
439
|
+
};
|
|
440
|
+
const handleResize = ()=>{
|
|
441
|
+
scheduleCalculate();
|
|
442
|
+
};
|
|
443
|
+
calculateActiveSection();
|
|
444
|
+
const deferredRecalcId = setTimeout(()=>{
|
|
445
|
+
calculateActiveSection();
|
|
446
|
+
}, 0);
|
|
447
|
+
scrollTarget.addEventListener("scroll", handleScroll, {
|
|
448
|
+
passive: true
|
|
449
|
+
});
|
|
450
|
+
window.addEventListener("resize", handleResize, {
|
|
451
|
+
passive: true
|
|
452
|
+
});
|
|
453
|
+
return ()=>{
|
|
454
|
+
clearTimeout(deferredRecalcId);
|
|
455
|
+
scrollTarget.removeEventListener("scroll", handleScroll);
|
|
456
|
+
window.removeEventListener("resize", handleResize);
|
|
457
|
+
if (rafId.current) {
|
|
458
|
+
cancelAnimationFrame(rafId.current);
|
|
459
|
+
rafId.current = null;
|
|
460
|
+
}
|
|
461
|
+
if (throttleTimeoutId.current) {
|
|
462
|
+
clearTimeout(throttleTimeoutId.current);
|
|
463
|
+
throttleTimeoutId.current = null;
|
|
464
|
+
}
|
|
465
|
+
if (programmaticScrollTimeoutId.current) {
|
|
466
|
+
clearTimeout(programmaticScrollTimeoutId.current);
|
|
467
|
+
programmaticScrollTimeoutId.current = null;
|
|
468
|
+
}
|
|
469
|
+
if (scrollIdleTimeoutRef.current) {
|
|
470
|
+
clearTimeout(scrollIdleTimeoutRef.current);
|
|
471
|
+
scrollIdleTimeoutRef.current = null;
|
|
472
|
+
}
|
|
473
|
+
scrollCleanupRef.current == null ? void 0 : scrollCleanupRef.current.call(scrollCleanupRef);
|
|
474
|
+
isThrottled.current = false;
|
|
475
|
+
hasPendingScroll.current = false;
|
|
476
|
+
isProgrammaticScrolling.current = false;
|
|
477
|
+
isScrollingRef.current = false;
|
|
478
|
+
};
|
|
479
|
+
}, [
|
|
480
|
+
calculateActiveSection,
|
|
481
|
+
debounceMs,
|
|
482
|
+
containerElement
|
|
483
|
+
]);
|
|
484
|
+
const activeIndex = useMemo(()=>{
|
|
485
|
+
var _sectionIndexMap_get;
|
|
486
|
+
if (!activeId) return -1;
|
|
487
|
+
return (_sectionIndexMap_get = sectionIndexMap.get(activeId)) != null ? _sectionIndexMap_get : -1;
|
|
488
|
+
}, [
|
|
489
|
+
activeId,
|
|
490
|
+
sectionIndexMap
|
|
491
|
+
]);
|
|
492
|
+
return {
|
|
493
|
+
activeId,
|
|
494
|
+
activeIndex,
|
|
495
|
+
scroll,
|
|
496
|
+
sections,
|
|
497
|
+
registerRef,
|
|
498
|
+
scrollToSection,
|
|
499
|
+
sectionProps,
|
|
500
|
+
navProps
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export { useDomet as default, useDomet };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A React hook for scroll tracking with smooth 60fps performance and smart hysteresis",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "blksmr",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/blksmr/domet.git"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://domet.blksmr.com
|
|
11
|
+
"homepage": "https://domet.blksmr.com",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/blksmr/domet/issues"
|
|
14
14
|
},
|
|
@@ -22,24 +22,30 @@
|
|
|
22
22
|
"active-section"
|
|
23
23
|
],
|
|
24
24
|
"type": "module",
|
|
25
|
-
"main": "./dist/index.js",
|
|
26
|
-
"module": "./dist/index.js",
|
|
27
|
-
"types": "./dist/index.d.ts",
|
|
25
|
+
"main": "./dist/es/index.js",
|
|
26
|
+
"module": "./dist/es/index.js",
|
|
27
|
+
"types": "./dist/es/index.d.ts",
|
|
28
28
|
"exports": {
|
|
29
29
|
".": {
|
|
30
|
-
"import":
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./dist/es/index.d.mts",
|
|
32
|
+
"default": "./dist/es/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"require": {
|
|
35
|
+
"types": "./dist/cjs/index.d.ts",
|
|
36
|
+
"default": "./dist/cjs/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"./useDomet": {}
|
|
33
40
|
},
|
|
34
41
|
"files": [
|
|
35
42
|
"dist"
|
|
36
43
|
],
|
|
37
|
-
"sideEffects": false,
|
|
38
44
|
"scripts": {
|
|
39
|
-
"
|
|
40
|
-
"dev": "
|
|
41
|
-
"
|
|
42
|
-
"lint
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"dev": "bunchee -w",
|
|
47
|
+
"build": "bunchee prepare && bunchee",
|
|
48
|
+
"lint": "biome check --fix --unsafe . --verbose",
|
|
43
49
|
"test": "vitest run",
|
|
44
50
|
"test:watch": "vitest",
|
|
45
51
|
"test:e2e": "playwright test",
|
|
@@ -61,6 +67,7 @@
|
|
|
61
67
|
"jsdom": "^25.0.1",
|
|
62
68
|
"react": "^18.3.1",
|
|
63
69
|
"react-dom": "^18.3.1",
|
|
70
|
+
"bunchee": "^6.6.0",
|
|
64
71
|
"typescript": "^5.8.3",
|
|
65
72
|
"vite": "^5.4.21",
|
|
66
73
|
"vitest": "^2.1.8"
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 blksmr
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default, useDomet } from "./useDomet";
|
package/dist/useDomet.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDomet.d.ts","sourceRoot":"","sources":["../src/useDomet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBvC,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACpE,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACvC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9D,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,YAAY,CAAC;IAC3C,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,QAAQ,CAAC;CACpC,CAAC;AAaF,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAAE,EACpB,YAAY,GAAE,SAAS,CAAC,WAAW,CAAC,GAAG,IAAW,EAClD,OAAO,GAAE,YAAiB,GACzB,cAAc,CAylBhB;AAED,eAAe,QAAQ,CAAC"}
|
package/dist/useScrowl.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useScrowl.d.ts","sourceRoot":"","sources":["../src/useScrowl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBvC,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACpE,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACvC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9D,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,YAAY,CAAC;IAC3C,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,QAAQ,CAAC;CACpC,CAAC;AAaF,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAAE,EACpB,YAAY,GAAE,SAAS,CAAC,WAAW,CAAC,GAAG,IAAW,EAClD,OAAO,GAAE,YAAiB,GACzB,cAAc,CAulBhB;AAED,eAAe,QAAQ,CAAC"}
|