domet 1.0.1 → 1.0.3
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/{useScrowl.js → cjs/index.js} +196 -177
- package/dist/{useScrowl.d.ts → es/index.d.mts} +14 -12
- package/dist/{useDomet.js → es/index.mjs} +167 -154
- package/package.json +20 -15
- 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
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
2
5
|
const DEFAULT_VISIBILITY_THRESHOLD = 0.6;
|
|
3
6
|
const DEFAULT_HYSTERESIS_MARGIN = 150;
|
|
4
7
|
const SCROLL_IDLE_MS = 100;
|
|
5
|
-
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
6
|
-
|
|
7
|
-
const { offset = 0, offsetRatio = 0.08, debounceMs = 10, visibilityThreshold = DEFAULT_VISIBILITY_THRESHOLD, hysteresisMargin = DEFAULT_HYSTERESIS_MARGIN, behavior = "auto", onActiveChange, onSectionEnter, onSectionLeave, onScrollStart, onScrollEnd
|
|
8
|
-
|
|
9
|
-
const stableSectionIds = useMemo(()
|
|
10
|
-
|
|
8
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
9
|
+
function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
10
|
+
const { offset = 0, offsetRatio = 0.08, debounceMs = 10, visibilityThreshold = DEFAULT_VISIBILITY_THRESHOLD, hysteresisMargin = DEFAULT_HYSTERESIS_MARGIN, behavior = "auto", onActiveChange, onSectionEnter, onSectionLeave, onScrollStart, onScrollEnd } = options;
|
|
11
|
+
JSON.stringify(sectionIds);
|
|
12
|
+
const stableSectionIds = react.useMemo(()=>sectionIds, [
|
|
13
|
+
sectionIds
|
|
14
|
+
]);
|
|
15
|
+
const sectionIndexMap = react.useMemo(()=>{
|
|
11
16
|
const map = new Map();
|
|
12
|
-
|
|
17
|
+
for(let i = 0; i < stableSectionIds.length; i++){
|
|
18
|
+
map.set(stableSectionIds[i], i);
|
|
19
|
+
}
|
|
13
20
|
return map;
|
|
14
|
-
}, [
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
}, [
|
|
22
|
+
stableSectionIds
|
|
23
|
+
]);
|
|
24
|
+
const [activeId, setActiveId] = react.useState(stableSectionIds[0] || null);
|
|
25
|
+
const [scroll, setScroll] = react.useState({
|
|
17
26
|
y: 0,
|
|
18
27
|
progress: 0,
|
|
19
28
|
direction: null,
|
|
@@ -21,74 +30,79 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
21
30
|
isScrolling: false,
|
|
22
31
|
maxScroll: 0,
|
|
23
32
|
viewportHeight: 0,
|
|
24
|
-
offset: 0
|
|
33
|
+
offset: 0
|
|
25
34
|
});
|
|
26
|
-
const [sections, setSections] = useState({});
|
|
27
|
-
const [containerElement, setContainerElement] = useState(null);
|
|
28
|
-
const refs = useRef({});
|
|
29
|
-
const refCallbacks = useRef({});
|
|
30
|
-
const activeIdRef = useRef(stableSectionIds[0] || null);
|
|
31
|
-
const lastScrollY = useRef(0);
|
|
32
|
-
const lastScrollTime = useRef(Date.now());
|
|
33
|
-
const rafId = useRef(null);
|
|
34
|
-
const isThrottled = useRef(false);
|
|
35
|
-
const throttleTimeoutId = useRef(null);
|
|
36
|
-
const hasPendingScroll = useRef(false);
|
|
37
|
-
const isProgrammaticScrolling = useRef(false);
|
|
38
|
-
const programmaticScrollTimeoutId = useRef(null);
|
|
39
|
-
const isScrollingRef = useRef(false);
|
|
40
|
-
const scrollIdleTimeoutRef = useRef(null);
|
|
41
|
-
const prevSectionsInViewport = useRef(new Set());
|
|
42
|
-
const recalculateRef = useRef(()
|
|
43
|
-
const scrollCleanupRef = useRef(null);
|
|
44
|
-
const callbackRefs = useRef({
|
|
35
|
+
const [sections, setSections] = react.useState({});
|
|
36
|
+
const [containerElement, setContainerElement] = react.useState(null);
|
|
37
|
+
const refs = react.useRef({});
|
|
38
|
+
const refCallbacks = react.useRef({});
|
|
39
|
+
const activeIdRef = react.useRef(stableSectionIds[0] || null);
|
|
40
|
+
const lastScrollY = react.useRef(0);
|
|
41
|
+
const lastScrollTime = react.useRef(Date.now());
|
|
42
|
+
const rafId = react.useRef(null);
|
|
43
|
+
const isThrottled = react.useRef(false);
|
|
44
|
+
const throttleTimeoutId = react.useRef(null);
|
|
45
|
+
const hasPendingScroll = react.useRef(false);
|
|
46
|
+
const isProgrammaticScrolling = react.useRef(false);
|
|
47
|
+
const programmaticScrollTimeoutId = react.useRef(null);
|
|
48
|
+
const isScrollingRef = react.useRef(false);
|
|
49
|
+
const scrollIdleTimeoutRef = react.useRef(null);
|
|
50
|
+
const prevSectionsInViewport = react.useRef(new Set());
|
|
51
|
+
const recalculateRef = react.useRef(()=>{});
|
|
52
|
+
const scrollCleanupRef = react.useRef(null);
|
|
53
|
+
const callbackRefs = react.useRef({
|
|
45
54
|
onActiveChange,
|
|
46
55
|
onSectionEnter,
|
|
47
56
|
onSectionLeave,
|
|
48
57
|
onScrollStart,
|
|
49
|
-
onScrollEnd
|
|
58
|
+
onScrollEnd
|
|
50
59
|
});
|
|
51
60
|
callbackRefs.current = {
|
|
52
61
|
onActiveChange,
|
|
53
62
|
onSectionEnter,
|
|
54
63
|
onSectionLeave,
|
|
55
64
|
onScrollStart,
|
|
56
|
-
onScrollEnd
|
|
65
|
+
onScrollEnd
|
|
57
66
|
};
|
|
58
|
-
const getEffectiveOffset = useCallback(()
|
|
67
|
+
const getEffectiveOffset = react.useCallback(()=>{
|
|
59
68
|
return offset;
|
|
60
|
-
}, [
|
|
61
|
-
|
|
69
|
+
}, [
|
|
70
|
+
offset
|
|
71
|
+
]);
|
|
72
|
+
const getScrollBehavior = react.useCallback(()=>{
|
|
62
73
|
if (behavior === "auto") {
|
|
63
|
-
if (typeof window === "undefined")
|
|
64
|
-
return "instant";
|
|
74
|
+
if (typeof window === "undefined") return "instant";
|
|
65
75
|
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
66
76
|
return prefersReducedMotion ? "instant" : "smooth";
|
|
67
77
|
}
|
|
68
78
|
return behavior;
|
|
69
|
-
}, [
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
}, [
|
|
80
|
+
behavior
|
|
81
|
+
]);
|
|
82
|
+
useIsomorphicLayoutEffect(()=>{
|
|
83
|
+
var _ref;
|
|
84
|
+
const nextContainer = (_ref = containerRef == null ? void 0 : containerRef.current) != null ? _ref : null;
|
|
72
85
|
if (nextContainer !== containerElement) {
|
|
73
86
|
setContainerElement(nextContainer);
|
|
74
87
|
}
|
|
75
|
-
}, [
|
|
76
|
-
|
|
88
|
+
}, [
|
|
89
|
+
containerRef,
|
|
90
|
+
containerElement
|
|
91
|
+
]);
|
|
92
|
+
const registerRef = react.useCallback((id)=>{
|
|
77
93
|
const existing = refCallbacks.current[id];
|
|
78
|
-
if (existing)
|
|
79
|
-
|
|
80
|
-
const callback = (el) => {
|
|
94
|
+
if (existing) return existing;
|
|
95
|
+
const callback = (el)=>{
|
|
81
96
|
if (el) {
|
|
82
97
|
refs.current[id] = el;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
98
|
+
} else {
|
|
85
99
|
delete refs.current[id];
|
|
86
100
|
}
|
|
87
101
|
};
|
|
88
102
|
refCallbacks.current[id] = callback;
|
|
89
103
|
return callback;
|
|
90
104
|
}, []);
|
|
91
|
-
const scrollToSection = useCallback((id)
|
|
105
|
+
const scrollToSection = react.useCallback((id)=>{
|
|
92
106
|
if (!stableSectionIds.includes(id)) {
|
|
93
107
|
if (process.env.NODE_ENV !== "production") {
|
|
94
108
|
console.warn(`[domet] scrollToSection: id "${id}" not in sectionIds`);
|
|
@@ -96,12 +110,11 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
96
110
|
return;
|
|
97
111
|
}
|
|
98
112
|
const element = refs.current[id];
|
|
99
|
-
if (!element)
|
|
100
|
-
return;
|
|
113
|
+
if (!element) return;
|
|
101
114
|
if (programmaticScrollTimeoutId.current) {
|
|
102
115
|
clearTimeout(programmaticScrollTimeoutId.current);
|
|
103
116
|
}
|
|
104
|
-
scrollCleanupRef.current
|
|
117
|
+
scrollCleanupRef.current == null ? void 0 : scrollCleanupRef.current.call(scrollCleanupRef);
|
|
105
118
|
isProgrammaticScrolling.current = true;
|
|
106
119
|
activeIdRef.current = id;
|
|
107
120
|
setActiveId(id);
|
|
@@ -109,19 +122,19 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
109
122
|
const elementRect = element.getBoundingClientRect();
|
|
110
123
|
const effectiveOffset = getEffectiveOffset() + 10;
|
|
111
124
|
const scrollTarget = container || window;
|
|
112
|
-
const unlockScroll = ()
|
|
125
|
+
const unlockScroll = ()=>{
|
|
113
126
|
isProgrammaticScrolling.current = false;
|
|
114
127
|
if (programmaticScrollTimeoutId.current) {
|
|
115
128
|
clearTimeout(programmaticScrollTimeoutId.current);
|
|
116
129
|
programmaticScrollTimeoutId.current = null;
|
|
117
130
|
}
|
|
118
|
-
requestAnimationFrame(()
|
|
131
|
+
requestAnimationFrame(()=>{
|
|
119
132
|
recalculateRef.current();
|
|
120
133
|
});
|
|
121
134
|
};
|
|
122
135
|
let debounceTimer = null;
|
|
123
136
|
let isUnlocked = false;
|
|
124
|
-
const cleanup = ()
|
|
137
|
+
const cleanup = ()=>{
|
|
125
138
|
if (debounceTimer) {
|
|
126
139
|
clearTimeout(debounceTimer);
|
|
127
140
|
debounceTimer = null;
|
|
@@ -132,31 +145,30 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
132
145
|
}
|
|
133
146
|
scrollCleanupRef.current = null;
|
|
134
147
|
};
|
|
135
|
-
const doUnlock = ()
|
|
136
|
-
if (isUnlocked)
|
|
137
|
-
return;
|
|
148
|
+
const doUnlock = ()=>{
|
|
149
|
+
if (isUnlocked) return;
|
|
138
150
|
isUnlocked = true;
|
|
139
151
|
cleanup();
|
|
140
152
|
unlockScroll();
|
|
141
153
|
};
|
|
142
|
-
const resetDebounce = ()
|
|
154
|
+
const resetDebounce = ()=>{
|
|
143
155
|
if (debounceTimer) {
|
|
144
156
|
clearTimeout(debounceTimer);
|
|
145
157
|
}
|
|
146
158
|
debounceTimer = setTimeout(doUnlock, SCROLL_IDLE_MS);
|
|
147
159
|
};
|
|
148
|
-
const handleScrollActivity = ()
|
|
160
|
+
const handleScrollActivity = ()=>{
|
|
149
161
|
resetDebounce();
|
|
150
162
|
};
|
|
151
|
-
const handleScrollEnd = ()
|
|
163
|
+
const handleScrollEnd = ()=>{
|
|
152
164
|
doUnlock();
|
|
153
165
|
};
|
|
154
166
|
scrollTarget.addEventListener("scroll", handleScrollActivity, {
|
|
155
|
-
passive: true
|
|
167
|
+
passive: true
|
|
156
168
|
});
|
|
157
169
|
if ("onscrollend" in scrollTarget) {
|
|
158
170
|
scrollTarget.addEventListener("scrollend", handleScrollEnd, {
|
|
159
|
-
once: true
|
|
171
|
+
once: true
|
|
160
172
|
});
|
|
161
173
|
}
|
|
162
174
|
scrollCleanupRef.current = cleanup;
|
|
@@ -166,121 +178,117 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
166
178
|
const relativeTop = elementRect.top - containerRect.top + container.scrollTop;
|
|
167
179
|
container.scrollTo({
|
|
168
180
|
top: relativeTop - effectiveOffset,
|
|
169
|
-
behavior: scrollBehavior
|
|
181
|
+
behavior: scrollBehavior
|
|
170
182
|
});
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
183
|
+
} else {
|
|
173
184
|
const absoluteTop = elementRect.top + window.scrollY;
|
|
174
185
|
window.scrollTo({
|
|
175
186
|
top: absoluteTop - effectiveOffset,
|
|
176
|
-
behavior: scrollBehavior
|
|
187
|
+
behavior: scrollBehavior
|
|
177
188
|
});
|
|
178
189
|
}
|
|
179
190
|
if (scrollBehavior === "instant") {
|
|
180
191
|
doUnlock();
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
192
|
+
} else {
|
|
183
193
|
resetDebounce();
|
|
184
194
|
}
|
|
185
|
-
}, [
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
}, [
|
|
196
|
+
stableSectionIds,
|
|
197
|
+
containerElement,
|
|
198
|
+
getEffectiveOffset,
|
|
199
|
+
getScrollBehavior
|
|
200
|
+
]);
|
|
201
|
+
const sectionProps = react.useCallback((id)=>({
|
|
202
|
+
id,
|
|
203
|
+
ref: registerRef(id),
|
|
204
|
+
"data-domet": id
|
|
205
|
+
}), [
|
|
206
|
+
registerRef
|
|
207
|
+
]);
|
|
208
|
+
const navProps = react.useCallback((id)=>({
|
|
209
|
+
onClick: ()=>scrollToSection(id),
|
|
210
|
+
"aria-current": activeId === id ? "page" : undefined,
|
|
211
|
+
"data-active": activeId === id
|
|
212
|
+
}), [
|
|
213
|
+
activeId,
|
|
214
|
+
scrollToSection
|
|
215
|
+
]);
|
|
216
|
+
react.useEffect(()=>{
|
|
217
|
+
var _stableSectionIds_;
|
|
197
218
|
const idsSet = new Set(stableSectionIds);
|
|
198
|
-
for (const id of Object.keys(refs.current))
|
|
219
|
+
for (const id of Object.keys(refs.current)){
|
|
199
220
|
if (!idsSet.has(id)) {
|
|
200
221
|
delete refs.current[id];
|
|
201
222
|
}
|
|
202
223
|
}
|
|
203
|
-
for (const id of Object.keys(refCallbacks.current))
|
|
224
|
+
for (const id of Object.keys(refCallbacks.current)){
|
|
204
225
|
if (!idsSet.has(id)) {
|
|
205
226
|
delete refCallbacks.current[id];
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
const currentActive = activeIdRef.current;
|
|
209
|
-
const nextActive = currentActive && idsSet.has(currentActive)
|
|
210
|
-
? currentActive
|
|
211
|
-
: (stableSectionIds[0] ?? null);
|
|
230
|
+
const nextActive = currentActive && idsSet.has(currentActive) ? currentActive : (_stableSectionIds_ = stableSectionIds[0]) != null ? _stableSectionIds_ : null;
|
|
212
231
|
if (nextActive !== currentActive) {
|
|
213
232
|
activeIdRef.current = nextActive;
|
|
214
233
|
}
|
|
215
|
-
setActiveId((prev)
|
|
216
|
-
}, [
|
|
217
|
-
|
|
234
|
+
setActiveId((prev)=>prev !== nextActive ? nextActive : prev);
|
|
235
|
+
}, [
|
|
236
|
+
stableSectionIds
|
|
237
|
+
]);
|
|
238
|
+
const getSectionBounds = react.useCallback(()=>{
|
|
218
239
|
const container = containerElement;
|
|
219
240
|
const scrollTop = container ? container.scrollTop : window.scrollY;
|
|
220
241
|
const containerTop = container ? container.getBoundingClientRect().top : 0;
|
|
221
|
-
return stableSectionIds
|
|
222
|
-
.map((id) => {
|
|
242
|
+
return stableSectionIds.map((id)=>{
|
|
223
243
|
const el = refs.current[id];
|
|
224
|
-
if (!el)
|
|
225
|
-
return null;
|
|
244
|
+
if (!el) return null;
|
|
226
245
|
const rect = el.getBoundingClientRect();
|
|
227
|
-
const relativeTop = container
|
|
228
|
-
? rect.top - containerTop + scrollTop
|
|
229
|
-
: rect.top + window.scrollY;
|
|
246
|
+
const relativeTop = container ? rect.top - containerTop + scrollTop : rect.top + window.scrollY;
|
|
230
247
|
return {
|
|
231
248
|
id,
|
|
232
249
|
top: relativeTop,
|
|
233
250
|
bottom: relativeTop + rect.height,
|
|
234
|
-
height: rect.height
|
|
251
|
+
height: rect.height
|
|
235
252
|
};
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
253
|
+
}).filter((bounds)=>bounds !== null);
|
|
254
|
+
}, [
|
|
255
|
+
stableSectionIds,
|
|
256
|
+
containerElement
|
|
257
|
+
]);
|
|
258
|
+
const calculateActiveSection = react.useCallback(()=>{
|
|
259
|
+
if (isProgrammaticScrolling.current) return;
|
|
242
260
|
const container = containerElement;
|
|
243
261
|
const currentActiveId = activeIdRef.current;
|
|
244
262
|
const now = Date.now();
|
|
245
263
|
const scrollY = container ? container.scrollTop : window.scrollY;
|
|
246
|
-
const viewportHeight = container
|
|
247
|
-
|
|
248
|
-
: window.innerHeight;
|
|
249
|
-
const scrollHeight = container
|
|
250
|
-
? container.scrollHeight
|
|
251
|
-
: document.documentElement.scrollHeight;
|
|
264
|
+
const viewportHeight = container ? container.clientHeight : window.innerHeight;
|
|
265
|
+
const scrollHeight = container ? container.scrollHeight : document.documentElement.scrollHeight;
|
|
252
266
|
const maxScroll = Math.max(0, scrollHeight - viewportHeight);
|
|
253
267
|
const scrollProgress = maxScroll > 0 ? scrollY / maxScroll : 0;
|
|
254
|
-
const scrollDirection = scrollY === lastScrollY.current
|
|
255
|
-
? null
|
|
256
|
-
: scrollY > lastScrollY.current
|
|
257
|
-
? "down"
|
|
258
|
-
: "up";
|
|
268
|
+
const scrollDirection = scrollY === lastScrollY.current ? null : scrollY > lastScrollY.current ? "down" : "up";
|
|
259
269
|
const deltaTime = now - lastScrollTime.current;
|
|
260
270
|
const deltaY = scrollY - lastScrollY.current;
|
|
261
271
|
const velocity = deltaTime > 0 ? Math.abs(deltaY) / deltaTime : 0;
|
|
262
272
|
lastScrollY.current = scrollY;
|
|
263
273
|
lastScrollTime.current = now;
|
|
264
274
|
const sectionBounds = getSectionBounds();
|
|
265
|
-
if (sectionBounds.length === 0)
|
|
266
|
-
return;
|
|
275
|
+
if (sectionBounds.length === 0) return;
|
|
267
276
|
const baseOffset = getEffectiveOffset();
|
|
268
277
|
const effectiveOffset = Math.max(baseOffset, viewportHeight * offsetRatio);
|
|
269
278
|
const triggerLine = scrollY + effectiveOffset;
|
|
270
279
|
const viewportTop = scrollY;
|
|
271
280
|
const viewportBottom = scrollY + viewportHeight;
|
|
272
|
-
const scores = sectionBounds.map((section)
|
|
281
|
+
const scores = sectionBounds.map((section)=>{
|
|
282
|
+
var _sectionIndexMap_get;
|
|
273
283
|
const visibleTop = Math.max(section.top, viewportTop);
|
|
274
284
|
const visibleBottom = Math.min(section.bottom, viewportBottom);
|
|
275
285
|
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
276
286
|
const visibilityRatio = section.height > 0 ? visibleHeight / section.height : 0;
|
|
277
287
|
const visibleInViewportRatio = viewportHeight > 0 ? visibleHeight / viewportHeight : 0;
|
|
278
288
|
const isInViewport = section.bottom > viewportTop && section.top < viewportBottom;
|
|
279
|
-
const sectionProgress = (()
|
|
280
|
-
if (section.height === 0)
|
|
281
|
-
return 0;
|
|
289
|
+
const sectionProgress = (()=>{
|
|
290
|
+
if (section.height === 0) return 0;
|
|
282
291
|
const entryPoint = viewportBottom;
|
|
283
|
-
const _exitPoint = viewportTop;
|
|
284
292
|
const totalTravel = viewportHeight + section.height;
|
|
285
293
|
const traveled = entryPoint - section.top;
|
|
286
294
|
return Math.max(0, Math.min(1, traveled / totalTravel));
|
|
@@ -288,15 +296,11 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
288
296
|
let score = 0;
|
|
289
297
|
if (visibilityRatio >= visibilityThreshold) {
|
|
290
298
|
score += 1000 + visibilityRatio * 500;
|
|
291
|
-
}
|
|
292
|
-
else if (isInViewport) {
|
|
299
|
+
} else if (isInViewport) {
|
|
293
300
|
score += visibleInViewportRatio * 800;
|
|
294
301
|
}
|
|
295
|
-
const sectionIndex = sectionIndexMap.get(section.id)
|
|
296
|
-
if (scrollDirection &&
|
|
297
|
-
isInViewport &&
|
|
298
|
-
section.top <= triggerLine &&
|
|
299
|
-
section.bottom > triggerLine) {
|
|
302
|
+
const sectionIndex = (_sectionIndexMap_get = sectionIndexMap.get(section.id)) != null ? _sectionIndexMap_get : 0;
|
|
303
|
+
if (scrollDirection && isInViewport && section.top <= triggerLine && section.bottom > triggerLine) {
|
|
300
304
|
score += 200;
|
|
301
305
|
}
|
|
302
306
|
score -= sectionIndex * 0.1;
|
|
@@ -306,47 +310,43 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
306
310
|
visibilityRatio,
|
|
307
311
|
isInViewport,
|
|
308
312
|
bounds: section,
|
|
309
|
-
progress: sectionProgress
|
|
313
|
+
progress: sectionProgress
|
|
310
314
|
};
|
|
311
315
|
});
|
|
312
|
-
const
|
|
313
|
-
const
|
|
316
|
+
const hasScroll = maxScroll > 10;
|
|
317
|
+
const isAtBottom = hasScroll && scrollY + viewportHeight >= scrollHeight - 5;
|
|
318
|
+
const isAtTop = hasScroll && scrollY <= 5;
|
|
314
319
|
let newActiveId = null;
|
|
315
320
|
if (isAtBottom && stableSectionIds.length > 0) {
|
|
316
321
|
newActiveId = stableSectionIds[stableSectionIds.length - 1];
|
|
317
|
-
}
|
|
318
|
-
else if (isAtTop && stableSectionIds.length > 0) {
|
|
322
|
+
} else if (isAtTop && stableSectionIds.length > 0) {
|
|
319
323
|
newActiveId = stableSectionIds[0];
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const visibleScores = scores.filter((s) => s.isInViewport);
|
|
324
|
+
} else {
|
|
325
|
+
const visibleScores = scores.filter((s)=>s.isInViewport);
|
|
323
326
|
const candidates = visibleScores.length > 0 ? visibleScores : scores;
|
|
324
|
-
candidates.sort((a, b)
|
|
327
|
+
candidates.sort((a, b)=>b.score - a.score);
|
|
325
328
|
if (candidates.length > 0) {
|
|
326
329
|
const bestCandidate = candidates[0];
|
|
327
|
-
const currentScore = scores.find((s)
|
|
328
|
-
const shouldSwitch = !currentScore ||
|
|
329
|
-
!currentScore.isInViewport ||
|
|
330
|
-
bestCandidate.score > currentScore.score + hysteresisMargin ||
|
|
331
|
-
bestCandidate.id === currentActiveId;
|
|
330
|
+
const currentScore = scores.find((s)=>s.id === currentActiveId);
|
|
331
|
+
const shouldSwitch = !currentScore || !currentScore.isInViewport || bestCandidate.score > currentScore.score + hysteresisMargin || bestCandidate.id === currentActiveId;
|
|
332
332
|
newActiveId = shouldSwitch ? bestCandidate.id : currentActiveId;
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
if (newActiveId !== currentActiveId) {
|
|
336
336
|
activeIdRef.current = newActiveId;
|
|
337
337
|
setActiveId(newActiveId);
|
|
338
|
-
callbackRefs.current.onActiveChange
|
|
338
|
+
callbackRefs.current.onActiveChange == null ? void 0 : callbackRefs.current.onActiveChange.call(callbackRefs.current, newActiveId, currentActiveId);
|
|
339
339
|
}
|
|
340
|
-
const currentInViewport = new Set(scores.filter((s)
|
|
340
|
+
const currentInViewport = new Set(scores.filter((s)=>s.isInViewport).map((s)=>s.id));
|
|
341
341
|
const prevInViewport = prevSectionsInViewport.current;
|
|
342
|
-
for (const id of currentInViewport)
|
|
342
|
+
for (const id of currentInViewport){
|
|
343
343
|
if (!prevInViewport.has(id)) {
|
|
344
|
-
callbackRefs.current.onSectionEnter
|
|
344
|
+
callbackRefs.current.onSectionEnter == null ? void 0 : callbackRefs.current.onSectionEnter.call(callbackRefs.current, id);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
-
for (const id of prevInViewport)
|
|
347
|
+
for (const id of prevInViewport){
|
|
348
348
|
if (!currentInViewport.has(id)) {
|
|
349
|
-
callbackRefs.current.onSectionLeave
|
|
349
|
+
callbackRefs.current.onSectionLeave == null ? void 0 : callbackRefs.current.onSectionLeave.call(callbackRefs.current, id);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
prevSectionsInViewport.current = currentInViewport;
|
|
@@ -358,20 +358,20 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
358
358
|
isScrolling: isScrollingRef.current,
|
|
359
359
|
maxScroll,
|
|
360
360
|
viewportHeight,
|
|
361
|
-
offset: effectiveOffset
|
|
361
|
+
offset: effectiveOffset
|
|
362
362
|
};
|
|
363
363
|
const newSections = {};
|
|
364
|
-
for (const s of scores)
|
|
364
|
+
for (const s of scores){
|
|
365
365
|
newSections[s.id] = {
|
|
366
366
|
bounds: {
|
|
367
367
|
top: s.bounds.top,
|
|
368
368
|
bottom: s.bounds.bottom,
|
|
369
|
-
height: s.bounds.height
|
|
369
|
+
height: s.bounds.height
|
|
370
370
|
},
|
|
371
371
|
visibility: Math.round(s.visibilityRatio * 100) / 100,
|
|
372
372
|
progress: Math.round(s.progress * 100) / 100,
|
|
373
373
|
isInViewport: s.isInViewport,
|
|
374
|
-
isActive: s.id === newActiveId
|
|
374
|
+
isActive: s.id === newActiveId
|
|
375
375
|
};
|
|
376
376
|
}
|
|
377
377
|
setScroll(newScrollState);
|
|
@@ -384,31 +384,37 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
384
384
|
visibilityThreshold,
|
|
385
385
|
hysteresisMargin,
|
|
386
386
|
getSectionBounds,
|
|
387
|
-
containerElement
|
|
387
|
+
containerElement
|
|
388
388
|
]);
|
|
389
389
|
recalculateRef.current = calculateActiveSection;
|
|
390
|
-
useEffect(()
|
|
390
|
+
react.useEffect(()=>{
|
|
391
391
|
const container = containerElement;
|
|
392
392
|
const scrollTarget = container || window;
|
|
393
|
-
const scheduleCalculate = ()
|
|
393
|
+
const scheduleCalculate = ()=>{
|
|
394
394
|
if (rafId.current) {
|
|
395
395
|
cancelAnimationFrame(rafId.current);
|
|
396
396
|
}
|
|
397
|
-
rafId.current = requestAnimationFrame(()
|
|
397
|
+
rafId.current = requestAnimationFrame(()=>{
|
|
398
398
|
rafId.current = null;
|
|
399
399
|
calculateActiveSection();
|
|
400
400
|
});
|
|
401
401
|
};
|
|
402
|
-
const handleScrollEnd = ()
|
|
402
|
+
const handleScrollEnd = ()=>{
|
|
403
403
|
isScrollingRef.current = false;
|
|
404
|
-
setScroll((prev)
|
|
405
|
-
|
|
404
|
+
setScroll((prev)=>({
|
|
405
|
+
...prev,
|
|
406
|
+
isScrolling: false
|
|
407
|
+
}));
|
|
408
|
+
callbackRefs.current.onScrollEnd == null ? void 0 : callbackRefs.current.onScrollEnd.call(callbackRefs.current);
|
|
406
409
|
};
|
|
407
|
-
const handleScroll = ()
|
|
410
|
+
const handleScroll = ()=>{
|
|
408
411
|
if (!isScrollingRef.current) {
|
|
409
412
|
isScrollingRef.current = true;
|
|
410
|
-
setScroll((prev)
|
|
411
|
-
|
|
413
|
+
setScroll((prev)=>({
|
|
414
|
+
...prev,
|
|
415
|
+
isScrolling: true
|
|
416
|
+
}));
|
|
417
|
+
callbackRefs.current.onScrollStart == null ? void 0 : callbackRefs.current.onScrollStart.call(callbackRefs.current);
|
|
412
418
|
}
|
|
413
419
|
if (scrollIdleTimeoutRef.current) {
|
|
414
420
|
clearTimeout(scrollIdleTimeoutRef.current);
|
|
@@ -424,7 +430,7 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
424
430
|
clearTimeout(throttleTimeoutId.current);
|
|
425
431
|
}
|
|
426
432
|
scheduleCalculate();
|
|
427
|
-
throttleTimeoutId.current = setTimeout(()
|
|
433
|
+
throttleTimeoutId.current = setTimeout(()=>{
|
|
428
434
|
isThrottled.current = false;
|
|
429
435
|
throttleTimeoutId.current = null;
|
|
430
436
|
if (hasPendingScroll.current) {
|
|
@@ -433,16 +439,20 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
433
439
|
}
|
|
434
440
|
}, debounceMs);
|
|
435
441
|
};
|
|
436
|
-
const handleResize = ()
|
|
442
|
+
const handleResize = ()=>{
|
|
437
443
|
scheduleCalculate();
|
|
438
444
|
};
|
|
439
445
|
calculateActiveSection();
|
|
440
|
-
const deferredRecalcId = setTimeout(()
|
|
446
|
+
const deferredRecalcId = setTimeout(()=>{
|
|
441
447
|
calculateActiveSection();
|
|
442
448
|
}, 0);
|
|
443
|
-
scrollTarget.addEventListener("scroll", handleScroll, {
|
|
444
|
-
|
|
445
|
-
|
|
449
|
+
scrollTarget.addEventListener("scroll", handleScroll, {
|
|
450
|
+
passive: true
|
|
451
|
+
});
|
|
452
|
+
window.addEventListener("resize", handleResize, {
|
|
453
|
+
passive: true
|
|
454
|
+
});
|
|
455
|
+
return ()=>{
|
|
446
456
|
clearTimeout(deferredRecalcId);
|
|
447
457
|
scrollTarget.removeEventListener("scroll", handleScroll);
|
|
448
458
|
window.removeEventListener("resize", handleResize);
|
|
@@ -462,18 +472,25 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
462
472
|
clearTimeout(scrollIdleTimeoutRef.current);
|
|
463
473
|
scrollIdleTimeoutRef.current = null;
|
|
464
474
|
}
|
|
465
|
-
scrollCleanupRef.current
|
|
475
|
+
scrollCleanupRef.current == null ? void 0 : scrollCleanupRef.current.call(scrollCleanupRef);
|
|
466
476
|
isThrottled.current = false;
|
|
467
477
|
hasPendingScroll.current = false;
|
|
468
478
|
isProgrammaticScrolling.current = false;
|
|
469
479
|
isScrollingRef.current = false;
|
|
470
480
|
};
|
|
471
|
-
}, [
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
481
|
+
}, [
|
|
482
|
+
calculateActiveSection,
|
|
483
|
+
debounceMs,
|
|
484
|
+
containerElement
|
|
485
|
+
]);
|
|
486
|
+
const activeIndex = react.useMemo(()=>{
|
|
487
|
+
var _sectionIndexMap_get;
|
|
488
|
+
if (!activeId) return -1;
|
|
489
|
+
return (_sectionIndexMap_get = sectionIndexMap.get(activeId)) != null ? _sectionIndexMap_get : -1;
|
|
490
|
+
}, [
|
|
491
|
+
activeId,
|
|
492
|
+
sectionIndexMap
|
|
493
|
+
]);
|
|
477
494
|
return {
|
|
478
495
|
activeId,
|
|
479
496
|
activeIndex,
|
|
@@ -482,7 +499,9 @@ export function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
482
499
|
registerRef,
|
|
483
500
|
scrollToSection,
|
|
484
501
|
sectionProps,
|
|
485
|
-
navProps
|
|
502
|
+
navProps
|
|
486
503
|
};
|
|
487
504
|
}
|
|
488
|
-
|
|
505
|
+
|
|
506
|
+
exports.default = useDomet;
|
|
507
|
+
exports.useDomet = useDomet;
|