domet 1.0.2 → 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/dist/cjs/index.js +40 -37
- package/package.json +4 -6
- package/dist/es/index.d.ts +0 -62
- package/dist/es/index.js +0 -504
package/dist/cjs/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
2
4
|
|
|
3
5
|
const DEFAULT_VISIBILITY_THRESHOLD = 0.6;
|
|
4
6
|
const DEFAULT_HYSTERESIS_MARGIN = 150;
|
|
5
7
|
const SCROLL_IDLE_MS = 100;
|
|
6
|
-
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
8
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
7
9
|
function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
8
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;
|
|
9
11
|
JSON.stringify(sectionIds);
|
|
10
|
-
const stableSectionIds = useMemo(()=>sectionIds, [
|
|
12
|
+
const stableSectionIds = react.useMemo(()=>sectionIds, [
|
|
11
13
|
sectionIds
|
|
12
14
|
]);
|
|
13
|
-
const sectionIndexMap = useMemo(()=>{
|
|
15
|
+
const sectionIndexMap = react.useMemo(()=>{
|
|
14
16
|
const map = new Map();
|
|
15
17
|
for(let i = 0; i < stableSectionIds.length; i++){
|
|
16
18
|
map.set(stableSectionIds[i], i);
|
|
@@ -19,8 +21,8 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
19
21
|
}, [
|
|
20
22
|
stableSectionIds
|
|
21
23
|
]);
|
|
22
|
-
const [activeId, setActiveId] = useState(stableSectionIds[0] || null);
|
|
23
|
-
const [scroll, setScroll] = useState({
|
|
24
|
+
const [activeId, setActiveId] = react.useState(stableSectionIds[0] || null);
|
|
25
|
+
const [scroll, setScroll] = react.useState({
|
|
24
26
|
y: 0,
|
|
25
27
|
progress: 0,
|
|
26
28
|
direction: null,
|
|
@@ -30,25 +32,25 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
30
32
|
viewportHeight: 0,
|
|
31
33
|
offset: 0
|
|
32
34
|
});
|
|
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({
|
|
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({
|
|
52
54
|
onActiveChange,
|
|
53
55
|
onSectionEnter,
|
|
54
56
|
onSectionLeave,
|
|
@@ -62,12 +64,12 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
62
64
|
onScrollStart,
|
|
63
65
|
onScrollEnd
|
|
64
66
|
};
|
|
65
|
-
const getEffectiveOffset = useCallback(()=>{
|
|
67
|
+
const getEffectiveOffset = react.useCallback(()=>{
|
|
66
68
|
return offset;
|
|
67
69
|
}, [
|
|
68
70
|
offset
|
|
69
71
|
]);
|
|
70
|
-
const getScrollBehavior = useCallback(()=>{
|
|
72
|
+
const getScrollBehavior = react.useCallback(()=>{
|
|
71
73
|
if (behavior === "auto") {
|
|
72
74
|
if (typeof window === "undefined") return "instant";
|
|
73
75
|
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -87,7 +89,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
87
89
|
containerRef,
|
|
88
90
|
containerElement
|
|
89
91
|
]);
|
|
90
|
-
const registerRef = useCallback((id)=>{
|
|
92
|
+
const registerRef = react.useCallback((id)=>{
|
|
91
93
|
const existing = refCallbacks.current[id];
|
|
92
94
|
if (existing) return existing;
|
|
93
95
|
const callback = (el)=>{
|
|
@@ -100,7 +102,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
100
102
|
refCallbacks.current[id] = callback;
|
|
101
103
|
return callback;
|
|
102
104
|
}, []);
|
|
103
|
-
const scrollToSection = useCallback((id)=>{
|
|
105
|
+
const scrollToSection = react.useCallback((id)=>{
|
|
104
106
|
if (!stableSectionIds.includes(id)) {
|
|
105
107
|
if (process.env.NODE_ENV !== "production") {
|
|
106
108
|
console.warn(`[domet] scrollToSection: id "${id}" not in sectionIds`);
|
|
@@ -196,14 +198,14 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
196
198
|
getEffectiveOffset,
|
|
197
199
|
getScrollBehavior
|
|
198
200
|
]);
|
|
199
|
-
const sectionProps = useCallback((id)=>({
|
|
201
|
+
const sectionProps = react.useCallback((id)=>({
|
|
200
202
|
id,
|
|
201
203
|
ref: registerRef(id),
|
|
202
204
|
"data-domet": id
|
|
203
205
|
}), [
|
|
204
206
|
registerRef
|
|
205
207
|
]);
|
|
206
|
-
const navProps = useCallback((id)=>({
|
|
208
|
+
const navProps = react.useCallback((id)=>({
|
|
207
209
|
onClick: ()=>scrollToSection(id),
|
|
208
210
|
"aria-current": activeId === id ? "page" : undefined,
|
|
209
211
|
"data-active": activeId === id
|
|
@@ -211,7 +213,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
211
213
|
activeId,
|
|
212
214
|
scrollToSection
|
|
213
215
|
]);
|
|
214
|
-
useEffect(()=>{
|
|
216
|
+
react.useEffect(()=>{
|
|
215
217
|
var _stableSectionIds_;
|
|
216
218
|
const idsSet = new Set(stableSectionIds);
|
|
217
219
|
for (const id of Object.keys(refs.current)){
|
|
@@ -233,7 +235,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
233
235
|
}, [
|
|
234
236
|
stableSectionIds
|
|
235
237
|
]);
|
|
236
|
-
const getSectionBounds = useCallback(()=>{
|
|
238
|
+
const getSectionBounds = react.useCallback(()=>{
|
|
237
239
|
const container = containerElement;
|
|
238
240
|
const scrollTop = container ? container.scrollTop : window.scrollY;
|
|
239
241
|
const containerTop = container ? container.getBoundingClientRect().top : 0;
|
|
@@ -253,7 +255,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
253
255
|
stableSectionIds,
|
|
254
256
|
containerElement
|
|
255
257
|
]);
|
|
256
|
-
const calculateActiveSection = useCallback(()=>{
|
|
258
|
+
const calculateActiveSection = react.useCallback(()=>{
|
|
257
259
|
if (isProgrammaticScrolling.current) return;
|
|
258
260
|
const container = containerElement;
|
|
259
261
|
const currentActiveId = activeIdRef.current;
|
|
@@ -385,7 +387,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
385
387
|
containerElement
|
|
386
388
|
]);
|
|
387
389
|
recalculateRef.current = calculateActiveSection;
|
|
388
|
-
useEffect(()=>{
|
|
390
|
+
react.useEffect(()=>{
|
|
389
391
|
const container = containerElement;
|
|
390
392
|
const scrollTarget = container || window;
|
|
391
393
|
const scheduleCalculate = ()=>{
|
|
@@ -481,7 +483,7 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
481
483
|
debounceMs,
|
|
482
484
|
containerElement
|
|
483
485
|
]);
|
|
484
|
-
const activeIndex = useMemo(()=>{
|
|
486
|
+
const activeIndex = react.useMemo(()=>{
|
|
485
487
|
var _sectionIndexMap_get;
|
|
486
488
|
if (!activeId) return -1;
|
|
487
489
|
return (_sectionIndexMap_get = sectionIndexMap.get(activeId)) != null ? _sectionIndexMap_get : -1;
|
|
@@ -501,4 +503,5 @@ function useDomet(sectionIds, containerRef = null, options = {}) {
|
|
|
501
503
|
};
|
|
502
504
|
}
|
|
503
505
|
|
|
504
|
-
|
|
506
|
+
exports.default = useDomet;
|
|
507
|
+
exports.useDomet = useDomet;
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domet",
|
|
3
|
-
"version": "1.0.2",
|
|
4
3
|
"description": "A React hook for scroll tracking with smooth 60fps performance and smart hysteresis",
|
|
5
|
-
"
|
|
4
|
+
"version": "1.0.3",
|
|
6
5
|
"author": "blksmr",
|
|
7
6
|
"repository": {
|
|
8
7
|
"type": "git",
|
|
@@ -21,10 +20,9 @@
|
|
|
21
20
|
"navigation",
|
|
22
21
|
"active-section"
|
|
23
22
|
],
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"types": "./dist/es/index.d.ts",
|
|
23
|
+
"main": "./dist/cjs/index.js",
|
|
24
|
+
"module": "./dist/es/index.mjs",
|
|
25
|
+
"types": "./dist/cjs/index.d.ts",
|
|
28
26
|
"exports": {
|
|
29
27
|
".": {
|
|
30
28
|
"import": {
|
package/dist/es/index.d.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { RefObject } from 'react';
|
|
2
|
-
|
|
3
|
-
type SectionBounds = {
|
|
4
|
-
top: number;
|
|
5
|
-
bottom: number;
|
|
6
|
-
height: number;
|
|
7
|
-
};
|
|
8
|
-
type ScrollState = {
|
|
9
|
-
y: number;
|
|
10
|
-
progress: number;
|
|
11
|
-
direction: "up" | "down" | null;
|
|
12
|
-
velocity: number;
|
|
13
|
-
isScrolling: boolean;
|
|
14
|
-
maxScroll: number;
|
|
15
|
-
viewportHeight: number;
|
|
16
|
-
offset: number;
|
|
17
|
-
};
|
|
18
|
-
type SectionState = {
|
|
19
|
-
bounds: SectionBounds;
|
|
20
|
-
visibility: number;
|
|
21
|
-
progress: number;
|
|
22
|
-
isInViewport: boolean;
|
|
23
|
-
isActive: boolean;
|
|
24
|
-
};
|
|
25
|
-
type ScrollBehavior = "smooth" | "instant" | "auto";
|
|
26
|
-
type DometOptions = {
|
|
27
|
-
offset?: number;
|
|
28
|
-
offsetRatio?: number;
|
|
29
|
-
debounceMs?: number;
|
|
30
|
-
visibilityThreshold?: number;
|
|
31
|
-
hysteresisMargin?: number;
|
|
32
|
-
behavior?: ScrollBehavior;
|
|
33
|
-
onActiveChange?: (id: string | null, prevId: string | null) => void;
|
|
34
|
-
onSectionEnter?: (id: string) => void;
|
|
35
|
-
onSectionLeave?: (id: string) => void;
|
|
36
|
-
onScrollStart?: () => void;
|
|
37
|
-
onScrollEnd?: () => void;
|
|
38
|
-
};
|
|
39
|
-
type SectionProps = {
|
|
40
|
-
id: string;
|
|
41
|
-
ref: (el: HTMLElement | null) => void;
|
|
42
|
-
"data-domet": string;
|
|
43
|
-
};
|
|
44
|
-
type NavProps = {
|
|
45
|
-
onClick: () => void;
|
|
46
|
-
"aria-current": "page" | undefined;
|
|
47
|
-
"data-active": boolean;
|
|
48
|
-
};
|
|
49
|
-
type UseDometReturn = {
|
|
50
|
-
activeId: string | null;
|
|
51
|
-
activeIndex: number;
|
|
52
|
-
scroll: ScrollState;
|
|
53
|
-
sections: Record<string, SectionState>;
|
|
54
|
-
registerRef: (id: string) => (el: HTMLElement | null) => void;
|
|
55
|
-
scrollToSection: (id: string) => void;
|
|
56
|
-
sectionProps: (id: string) => SectionProps;
|
|
57
|
-
navProps: (id: string) => NavProps;
|
|
58
|
-
};
|
|
59
|
-
declare function useDomet(sectionIds: string[], containerRef?: RefObject<HTMLElement> | null, options?: DometOptions): UseDometReturn;
|
|
60
|
-
|
|
61
|
-
export { useDomet as default, useDomet };
|
|
62
|
-
export type { DometOptions, NavProps, ScrollBehavior, ScrollState, SectionBounds, SectionProps, SectionState, UseDometReturn };
|
package/dist/es/index.js
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
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 };
|