scroll-system 1.0.1 → 1.1.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/README.md +138 -3
- package/dist/components/AriaLiveRegion.d.ts +18 -0
- package/dist/components/AriaLiveRegion.d.ts.map +1 -0
- package/dist/components/ControlledView.d.ts +16 -0
- package/dist/components/ControlledView.d.ts.map +1 -0
- package/dist/components/FullView.d.ts +20 -0
- package/dist/components/FullView.d.ts.map +1 -0
- package/dist/components/LazyView.d.ts +33 -0
- package/dist/components/LazyView.d.ts.map +1 -0
- package/dist/components/NestedScrollView.d.ts +38 -0
- package/dist/components/NestedScrollView.d.ts.map +1 -0
- package/dist/components/ScrollContainer.d.ts +9 -0
- package/dist/components/ScrollContainer.d.ts.map +1 -0
- package/dist/components/ScrollDebugOverlay.d.ts +17 -0
- package/dist/components/ScrollDebugOverlay.d.ts.map +1 -0
- package/dist/components/ScrollLockedView.d.ts +10 -0
- package/dist/components/ScrollLockedView.d.ts.map +1 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/constants.d.ts +17 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +21 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useAutoScroll.d.ts +41 -0
- package/dist/hooks/useAutoScroll.d.ts.map +1 -0
- package/dist/hooks/useDragHandler.d.ts +28 -0
- package/dist/hooks/useDragHandler.d.ts.map +1 -0
- package/dist/hooks/useFocusManagement.d.ts +19 -0
- package/dist/hooks/useFocusManagement.d.ts.map +1 -0
- package/dist/hooks/useGestureConfig.d.ts +37 -0
- package/dist/hooks/useGestureConfig.d.ts.map +1 -0
- package/dist/hooks/useGlobalProgress.d.ts +36 -0
- package/dist/hooks/useGlobalProgress.d.ts.map +1 -0
- package/dist/hooks/useHashSync.d.ts +24 -0
- package/dist/hooks/useHashSync.d.ts.map +1 -0
- package/dist/hooks/useInfiniteScroll.d.ts +41 -0
- package/dist/hooks/useInfiniteScroll.d.ts.map +1 -0
- package/dist/hooks/useKeyboardHandler.d.ts +20 -0
- package/dist/hooks/useKeyboardHandler.d.ts.map +1 -0
- package/dist/hooks/useMetricsReporter.d.ts +23 -0
- package/dist/hooks/useMetricsReporter.d.ts.map +1 -0
- package/dist/hooks/useNavigation.d.ts +42 -0
- package/dist/hooks/useNavigation.d.ts.map +1 -0
- package/dist/hooks/useParallax.d.ts +33 -0
- package/dist/hooks/useParallax.d.ts.map +1 -0
- package/dist/hooks/usePreload.d.ts +31 -0
- package/dist/hooks/usePreload.d.ts.map +1 -0
- package/dist/hooks/useScrollAnalytics.d.ts +40 -0
- package/dist/hooks/useScrollAnalytics.d.ts.map +1 -0
- package/dist/hooks/useScrollLock.d.ts +40 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -0
- package/dist/hooks/useScrollSystem.d.ts +16 -0
- package/dist/hooks/useScrollSystem.d.ts.map +1 -0
- package/dist/hooks/useSnapPoints.d.ts +77 -0
- package/dist/hooks/useSnapPoints.d.ts.map +1 -0
- package/dist/hooks/useTouchHandler.d.ts +12 -0
- package/dist/hooks/useTouchHandler.d.ts.map +1 -0
- package/dist/hooks/useViewProgress.d.ts +22 -0
- package/dist/hooks/useViewProgress.d.ts.map +1 -0
- package/dist/hooks/useViewRegistration.d.ts +25 -0
- package/dist/hooks/useViewRegistration.d.ts.map +1 -0
- package/dist/hooks/useWheelHandler.d.ts +8 -0
- package/dist/hooks/useWheelHandler.d.ts.map +1 -0
- package/dist/index.d.ts +16 -2009
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +860 -284
- package/dist/index.mjs +799 -212
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/navigation.store.d.ts +23 -0
- package/dist/store/navigation.store.d.ts.map +1 -0
- package/dist/types/index.d.ts +315 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/index.d.ts +21 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/normalizeWheel.d.ts +10 -0
- package/dist/utils/normalizeWheel.d.ts.map +1 -0
- package/package.json +4 -2
- package/dist/index.d.mts +0 -2010
package/dist/index.js
CHANGED
|
@@ -1,65 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
'use strict';
|
|
19
2
|
|
|
20
|
-
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ControlledView: () => ControlledView,
|
|
25
|
-
DEFAULT_PROGRESS_DEBOUNCE: () => DEFAULT_PROGRESS_DEBOUNCE,
|
|
26
|
-
DEFAULT_TRANSITION_DURATION: () => DEFAULT_TRANSITION_DURATION,
|
|
27
|
-
DEFAULT_TRANSITION_EASING: () => DEFAULT_TRANSITION_EASING,
|
|
28
|
-
FullView: () => FullView,
|
|
29
|
-
LazyView: () => LazyView,
|
|
30
|
-
NAVIGATION_COOLDOWN: () => NAVIGATION_COOLDOWN,
|
|
31
|
-
NAV_THRESHOLDS: () => NAV_THRESHOLDS,
|
|
32
|
-
ScrollContainer: () => ScrollContainer,
|
|
33
|
-
ScrollDebugOverlay: () => ScrollDebugOverlay,
|
|
34
|
-
ScrollLockedView: () => ScrollLockedView,
|
|
35
|
-
selectActiveView: () => selectActiveView,
|
|
36
|
-
selectActiveViewProgress: () => selectActiveViewProgress,
|
|
37
|
-
selectCanNavigateNext: () => selectCanNavigateNext,
|
|
38
|
-
selectCanNavigatePrevious: () => selectCanNavigatePrevious,
|
|
39
|
-
useActiveViewProgress: () => useActiveViewProgress,
|
|
40
|
-
useDragHandler: () => useDragHandler,
|
|
41
|
-
useFocusManagement: () => useFocusManagement,
|
|
42
|
-
useHashSync: () => useHashSync,
|
|
43
|
-
useKeyboardHandler: () => useKeyboardHandler,
|
|
44
|
-
useMetricsReporter: () => useMetricsReporter,
|
|
45
|
-
useNavigation: () => useNavigation,
|
|
46
|
-
useScrollAnalytics: () => useScrollAnalytics,
|
|
47
|
-
useScrollStore: () => useScrollStore,
|
|
48
|
-
useScrollSystem: () => useScrollSystem,
|
|
49
|
-
useTouchHandler: () => useTouchHandler,
|
|
50
|
-
useViewControl: () => useViewControl,
|
|
51
|
-
useViewProgress: () => useViewProgress,
|
|
52
|
-
useViewRegistration: () => useViewRegistration,
|
|
53
|
-
useWheelHandler: () => useWheelHandler
|
|
54
|
-
});
|
|
55
|
-
module.exports = __toCommonJS(index_exports);
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var zustand = require('zustand');
|
|
5
|
+
var middleware = require('zustand/middleware');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
56
7
|
|
|
57
8
|
// components/ScrollContainer.tsx
|
|
58
|
-
var import_react8 = require("react");
|
|
59
|
-
|
|
60
|
-
// store/navigation.store.ts
|
|
61
|
-
var import_zustand = require("zustand");
|
|
62
|
-
var import_middleware = require("zustand/middleware");
|
|
63
9
|
|
|
64
10
|
// constants.ts
|
|
65
11
|
var DEFAULT_TRANSITION_DURATION = 700;
|
|
@@ -78,6 +24,7 @@ function evaluateStateMachine(capability, progress, viewType, explicitLock) {
|
|
|
78
24
|
if (explicitLock) return explicitLock;
|
|
79
25
|
if (capability === "none") return "unlocked";
|
|
80
26
|
if (viewType === "full") return "unlocked";
|
|
27
|
+
if (viewType === "nested") return "unlocked";
|
|
81
28
|
if (progress >= 0.99) return "unlocked";
|
|
82
29
|
return "locked";
|
|
83
30
|
}
|
|
@@ -101,11 +48,16 @@ var initialState = {
|
|
|
101
48
|
isTransitioning: false,
|
|
102
49
|
isGlobalLocked: false,
|
|
103
50
|
isDragging: false,
|
|
104
|
-
globalProgress: 0
|
|
51
|
+
globalProgress: 0,
|
|
52
|
+
// NEW: AutoScroll state
|
|
53
|
+
isAutoScrolling: false,
|
|
54
|
+
isAutoScrollPaused: false,
|
|
55
|
+
// NEW: Infinite scroll
|
|
56
|
+
infiniteScrollEnabled: false
|
|
105
57
|
};
|
|
106
58
|
var lastNavigationTime = 0;
|
|
107
|
-
var useScrollStore =
|
|
108
|
-
|
|
59
|
+
var useScrollStore = zustand.create()(
|
|
60
|
+
middleware.subscribeWithSelector((set, get) => ({
|
|
109
61
|
...initialState,
|
|
110
62
|
initialize: () => {
|
|
111
63
|
const { views } = get();
|
|
@@ -126,12 +78,15 @@ var useScrollStore = (0, import_zustand.create)()(
|
|
|
126
78
|
index: newIndex,
|
|
127
79
|
type: config.type,
|
|
128
80
|
isActive: newIndex === 0,
|
|
81
|
+
isPreloaded: newIndex <= 1,
|
|
82
|
+
// Preload first 2 views by default
|
|
129
83
|
capability: "none",
|
|
130
84
|
navigation: "unlocked",
|
|
131
85
|
explicitLock: null,
|
|
132
86
|
progress: 0,
|
|
133
87
|
metrics: { scrollHeight: 0, clientHeight: 0, scrollTop: 0 },
|
|
134
|
-
config
|
|
88
|
+
config,
|
|
89
|
+
activeSnapPointId: null
|
|
135
90
|
};
|
|
136
91
|
const newViews = [...state.views, newView];
|
|
137
92
|
return {
|
|
@@ -172,7 +127,10 @@ var useScrollStore = (0, import_zustand.create)()(
|
|
|
172
127
|
progress,
|
|
173
128
|
navigation
|
|
174
129
|
};
|
|
175
|
-
|
|
130
|
+
const activeView = newViews[state.activeIndex];
|
|
131
|
+
const viewProgress = activeView?.progress ?? 0;
|
|
132
|
+
const globalProgress = (state.activeIndex + viewProgress) / state.totalViews;
|
|
133
|
+
return { views: newViews, globalProgress };
|
|
176
134
|
});
|
|
177
135
|
},
|
|
178
136
|
processIntention: (intention) => {
|
|
@@ -183,10 +141,15 @@ var useScrollStore = (0, import_zustand.create)()(
|
|
|
183
141
|
if (intention.type === "navigate") {
|
|
184
142
|
if (intention.direction === "down") {
|
|
185
143
|
if (activeView.navigation === "locked") return false;
|
|
186
|
-
if (state.activeIndex
|
|
187
|
-
|
|
188
|
-
|
|
144
|
+
if (state.activeIndex >= state.totalViews - 1) {
|
|
145
|
+
if (state.infiniteScrollEnabled) {
|
|
146
|
+
get().goToView(0);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
189
150
|
}
|
|
151
|
+
get().goToView(state.activeIndex + 1);
|
|
152
|
+
return true;
|
|
190
153
|
} else if (intention.direction === "up") {
|
|
191
154
|
const isAtTop = activeView.metrics.scrollTop <= 1;
|
|
192
155
|
if (activeView.capability === "internal" && !isAtTop) {
|
|
@@ -200,21 +163,28 @@ var useScrollStore = (0, import_zustand.create)()(
|
|
|
200
163
|
} else {
|
|
201
164
|
if (activeView.explicitLock === "locked") return false;
|
|
202
165
|
}
|
|
203
|
-
if (state.activeIndex
|
|
204
|
-
|
|
205
|
-
|
|
166
|
+
if (state.activeIndex <= 0) {
|
|
167
|
+
if (state.infiniteScrollEnabled) {
|
|
168
|
+
get().goToView(state.totalViews - 1);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
206
172
|
}
|
|
173
|
+
get().goToView(state.activeIndex - 1);
|
|
174
|
+
return true;
|
|
207
175
|
}
|
|
208
176
|
}
|
|
209
177
|
return false;
|
|
210
178
|
},
|
|
211
179
|
goToNext: () => {
|
|
212
180
|
const state = get();
|
|
213
|
-
state.
|
|
181
|
+
const nextIndex = state.infiniteScrollEnabled && state.activeIndex >= state.totalViews - 1 ? 0 : state.activeIndex + 1;
|
|
182
|
+
state.goToView(nextIndex);
|
|
214
183
|
},
|
|
215
184
|
goToPrevious: () => {
|
|
216
185
|
const state = get();
|
|
217
|
-
state.
|
|
186
|
+
const prevIndex = state.infiniteScrollEnabled && state.activeIndex <= 0 ? state.totalViews - 1 : state.activeIndex - 1;
|
|
187
|
+
state.goToView(prevIndex);
|
|
218
188
|
},
|
|
219
189
|
goToView: (indexOrId) => {
|
|
220
190
|
const state = get();
|
|
@@ -222,19 +192,29 @@ var useScrollStore = (0, import_zustand.create)()(
|
|
|
222
192
|
if (now - lastNavigationTime < NAVIGATION_COOLDOWN) return;
|
|
223
193
|
lastNavigationTime = now;
|
|
224
194
|
let targetIndex = typeof indexOrId === "string" ? state.views.findIndex((v) => v.id === indexOrId) : indexOrId;
|
|
195
|
+
if (state.infiniteScrollEnabled) {
|
|
196
|
+
if (targetIndex < 0) targetIndex = state.totalViews - 1;
|
|
197
|
+
if (targetIndex >= state.totalViews) targetIndex = 0;
|
|
198
|
+
}
|
|
225
199
|
if (targetIndex < 0 || targetIndex >= state.totalViews) return;
|
|
226
200
|
if (targetIndex === state.activeIndex) return;
|
|
227
|
-
set((s) =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
201
|
+
set((s) => {
|
|
202
|
+
const newViews = s.views.map((v, idx) => {
|
|
203
|
+
const isAdjacent = Math.abs(idx - targetIndex) <= 1 || s.infiniteScrollEnabled && (targetIndex === 0 && idx === s.totalViews - 1 || targetIndex === s.totalViews - 1 && idx === 0);
|
|
204
|
+
return {
|
|
205
|
+
...v,
|
|
206
|
+
isActive: idx === targetIndex,
|
|
207
|
+
isPreloaded: isAdjacent || idx === targetIndex
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
...s,
|
|
212
|
+
isTransitioning: true,
|
|
213
|
+
activeIndex: targetIndex,
|
|
214
|
+
activeId: newViews[targetIndex]?.id ?? null,
|
|
215
|
+
views: newViews
|
|
216
|
+
};
|
|
217
|
+
});
|
|
238
218
|
},
|
|
239
219
|
setViewExplicitLock: (id, lock) => {
|
|
240
220
|
set((state) => {
|
|
@@ -249,9 +229,33 @@ var useScrollStore = (0, import_zustand.create)()(
|
|
|
249
229
|
},
|
|
250
230
|
setGlobalLock: (locked) => set({ isGlobalLocked: locked }),
|
|
251
231
|
setDragging: (dragging) => set({ isDragging: dragging }),
|
|
252
|
-
getViewById: (id) => get().views.find((v) => v.id === id),
|
|
253
232
|
startTransition: () => set({ isTransitioning: true }),
|
|
254
233
|
endTransition: () => set({ isTransitioning: false }),
|
|
234
|
+
// NEW: AutoScroll control
|
|
235
|
+
setAutoScrolling: (enabled) => set({ isAutoScrolling: enabled }),
|
|
236
|
+
setAutoScrollPaused: (paused) => set({ isAutoScrollPaused: paused }),
|
|
237
|
+
// NEW: Infinite scroll
|
|
238
|
+
setInfiniteScrollEnabled: (enabled) => set({ infiniteScrollEnabled: enabled }),
|
|
239
|
+
// NEW: Preload
|
|
240
|
+
setViewPreloaded: (id, preloaded) => {
|
|
241
|
+
set((state) => {
|
|
242
|
+
const index = state.views.findIndex((v) => v.id === id);
|
|
243
|
+
if (index === -1) return state;
|
|
244
|
+
const newViews = [...state.views];
|
|
245
|
+
newViews[index] = { ...newViews[index], isPreloaded: preloaded };
|
|
246
|
+
return { views: newViews };
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
// NEW: Snap points
|
|
250
|
+
setActiveSnapPoint: (viewId, snapPointId) => {
|
|
251
|
+
set((state) => {
|
|
252
|
+
const index = state.views.findIndex((v) => v.id === viewId);
|
|
253
|
+
if (index === -1) return state;
|
|
254
|
+
const newViews = [...state.views];
|
|
255
|
+
newViews[index] = { ...newViews[index], activeSnapPointId: snapPointId };
|
|
256
|
+
return { views: newViews };
|
|
257
|
+
});
|
|
258
|
+
},
|
|
255
259
|
resetNavigationCooldown: () => {
|
|
256
260
|
lastNavigationTime = 0;
|
|
257
261
|
}
|
|
@@ -263,15 +267,16 @@ var selectCanNavigateNext = (state) => {
|
|
|
263
267
|
if (state.isTransitioning || state.isGlobalLocked) return false;
|
|
264
268
|
const activeView = state.views[state.activeIndex];
|
|
265
269
|
if (!activeView) return false;
|
|
266
|
-
return activeView.navigation === "unlocked";
|
|
270
|
+
if (state.infiniteScrollEnabled) return activeView.navigation === "unlocked";
|
|
271
|
+
return activeView.navigation === "unlocked" && state.activeIndex < state.totalViews - 1;
|
|
267
272
|
};
|
|
268
273
|
var selectCanNavigatePrevious = (state) => {
|
|
269
274
|
if (state.isTransitioning || state.isGlobalLocked) return false;
|
|
275
|
+
if (state.infiniteScrollEnabled) return state.activeIndex >= 0;
|
|
270
276
|
return state.activeIndex > 0;
|
|
271
277
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
var import_react = require("react");
|
|
278
|
+
var selectGlobalProgress = (state) => state.globalProgress;
|
|
279
|
+
var selectIsAutoScrolling = (state) => state.isAutoScrolling && !state.isAutoScrollPaused;
|
|
275
280
|
|
|
276
281
|
// utils/normalizeWheel.ts
|
|
277
282
|
function normalizeWheel(event) {
|
|
@@ -308,10 +313,10 @@ function normalizeWheel(event) {
|
|
|
308
313
|
|
|
309
314
|
// hooks/useWheelHandler.ts
|
|
310
315
|
function useWheelHandler() {
|
|
311
|
-
const scrollAccumulator =
|
|
312
|
-
const lastScrollTime =
|
|
313
|
-
|
|
314
|
-
|
|
316
|
+
const scrollAccumulator = react.useRef(0);
|
|
317
|
+
const lastScrollTime = react.useRef(0);
|
|
318
|
+
useScrollStore((s) => s.isTransitioning);
|
|
319
|
+
react.useEffect(() => {
|
|
315
320
|
const handleWheel = (event) => {
|
|
316
321
|
const state = useScrollStore.getState();
|
|
317
322
|
if (state.isTransitioning || state.isDragging) {
|
|
@@ -338,7 +343,6 @@ function useWheelHandler() {
|
|
|
338
343
|
if (handled) {
|
|
339
344
|
scrollAccumulator.current = 0;
|
|
340
345
|
event.preventDefault();
|
|
341
|
-
} else {
|
|
342
346
|
}
|
|
343
347
|
}
|
|
344
348
|
};
|
|
@@ -346,14 +350,11 @@ function useWheelHandler() {
|
|
|
346
350
|
return () => window.removeEventListener("wheel", handleWheel);
|
|
347
351
|
}, []);
|
|
348
352
|
}
|
|
349
|
-
|
|
350
|
-
// hooks/useTouchHandler.ts
|
|
351
|
-
var import_react2 = require("react");
|
|
352
353
|
function useTouchHandler(options = {}) {
|
|
353
354
|
const { enabled = true } = options;
|
|
354
|
-
const touchStart =
|
|
355
|
-
const touchStartTime =
|
|
356
|
-
|
|
355
|
+
const touchStart = react.useRef(null);
|
|
356
|
+
const touchStartTime = react.useRef(0);
|
|
357
|
+
react.useEffect(() => {
|
|
357
358
|
if (!enabled) return;
|
|
358
359
|
const handleTouchStart = (e) => {
|
|
359
360
|
touchStart.current = {
|
|
@@ -394,12 +395,9 @@ function useTouchHandler(options = {}) {
|
|
|
394
395
|
};
|
|
395
396
|
}, [enabled]);
|
|
396
397
|
}
|
|
397
|
-
|
|
398
|
-
// hooks/useKeyboardHandler.ts
|
|
399
|
-
var import_react3 = require("react");
|
|
400
398
|
function useKeyboardHandler(options = {}) {
|
|
401
399
|
const { enabled = true, preventDefault = true } = options;
|
|
402
|
-
|
|
400
|
+
react.useEffect(() => {
|
|
403
401
|
if (!enabled) return;
|
|
404
402
|
const handleKeyDown = (e) => {
|
|
405
403
|
const target = e.target;
|
|
@@ -457,13 +455,10 @@ function useKeyboardHandler(options = {}) {
|
|
|
457
455
|
};
|
|
458
456
|
}, [enabled, preventDefault]);
|
|
459
457
|
}
|
|
460
|
-
|
|
461
|
-
// hooks/useHashSync.ts
|
|
462
|
-
var import_react4 = require("react");
|
|
463
458
|
function useHashSync(options = {}) {
|
|
464
459
|
const { enabled = true, pushHistory = false, hashPrefix = "" } = options;
|
|
465
|
-
const hasInitialized =
|
|
466
|
-
|
|
460
|
+
const hasInitialized = react.useRef(false);
|
|
461
|
+
react.useEffect(() => {
|
|
467
462
|
if (!enabled) return;
|
|
468
463
|
const unsubscribe = useScrollStore.subscribe(
|
|
469
464
|
(state) => state.activeIndex,
|
|
@@ -484,7 +479,7 @@ function useHashSync(options = {}) {
|
|
|
484
479
|
);
|
|
485
480
|
return () => unsubscribe();
|
|
486
481
|
}, [enabled, pushHistory, hashPrefix]);
|
|
487
|
-
|
|
482
|
+
react.useEffect(() => {
|
|
488
483
|
if (!enabled) return;
|
|
489
484
|
const handlePopState = () => {
|
|
490
485
|
const hash = window.location.hash.slice(1);
|
|
@@ -499,7 +494,7 @@ function useHashSync(options = {}) {
|
|
|
499
494
|
window.addEventListener("popstate", handlePopState);
|
|
500
495
|
return () => window.removeEventListener("popstate", handlePopState);
|
|
501
496
|
}, [enabled, hashPrefix]);
|
|
502
|
-
|
|
497
|
+
react.useEffect(() => {
|
|
503
498
|
if (!enabled) return;
|
|
504
499
|
const checkAndNavigate = () => {
|
|
505
500
|
const state = useScrollStore.getState();
|
|
@@ -522,30 +517,27 @@ function useHashSync(options = {}) {
|
|
|
522
517
|
checkAndNavigate();
|
|
523
518
|
}, [enabled, hashPrefix]);
|
|
524
519
|
}
|
|
525
|
-
|
|
526
|
-
// hooks/useDragHandler.ts
|
|
527
|
-
var import_react5 = require("react");
|
|
528
520
|
var DRAG_THRESHOLD = 50;
|
|
529
521
|
var VELOCITY_THRESHOLD = 0.5;
|
|
530
522
|
var RESISTANCE_FACTOR = 0.4;
|
|
531
523
|
function useDragHandler(options = {}) {
|
|
532
524
|
const { enabled = true, onDragUpdate, onDragEnd } = options;
|
|
533
|
-
const [dragState, setDragState] =
|
|
525
|
+
const [dragState, setDragState] = react.useState({
|
|
534
526
|
isDragging: false,
|
|
535
527
|
dragOffset: 0,
|
|
536
528
|
dragDirection: null
|
|
537
529
|
});
|
|
538
|
-
const touchStartRef =
|
|
539
|
-
const lastMoveRef =
|
|
540
|
-
const rafRef =
|
|
541
|
-
const updateDragState =
|
|
530
|
+
const touchStartRef = react.useRef(null);
|
|
531
|
+
const lastMoveRef = react.useRef(null);
|
|
532
|
+
const rafRef = react.useRef(null);
|
|
533
|
+
const updateDragState = react.useCallback((newState) => {
|
|
542
534
|
setDragState((prev) => {
|
|
543
535
|
const updated = { ...prev, ...newState };
|
|
544
536
|
onDragUpdate?.(updated);
|
|
545
537
|
return updated;
|
|
546
538
|
});
|
|
547
539
|
}, [onDragUpdate]);
|
|
548
|
-
|
|
540
|
+
react.useEffect(() => {
|
|
549
541
|
if (!enabled) return;
|
|
550
542
|
const handleTouchStart = (e) => {
|
|
551
543
|
const target = e.target;
|
|
@@ -631,16 +623,13 @@ function useDragHandler(options = {}) {
|
|
|
631
623
|
}, [enabled, updateDragState, onDragEnd]);
|
|
632
624
|
return dragState;
|
|
633
625
|
}
|
|
634
|
-
|
|
635
|
-
// hooks/useFocusManagement.ts
|
|
636
|
-
var import_react6 = require("react");
|
|
637
626
|
function useFocusManagement(options = {}) {
|
|
638
627
|
const { enabled = true, focusDelay = 100 } = options;
|
|
639
628
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
640
629
|
const activeId = useScrollStore((s) => s.activeId);
|
|
641
630
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
642
|
-
const prevIndexRef =
|
|
643
|
-
|
|
631
|
+
const prevIndexRef = react.useRef(activeIndex);
|
|
632
|
+
react.useEffect(() => {
|
|
644
633
|
if (!enabled) return;
|
|
645
634
|
if (activeIndex !== prevIndexRef.current && !isTransitioning && activeId) {
|
|
646
635
|
const timer = setTimeout(() => {
|
|
@@ -657,9 +646,6 @@ function useFocusManagement(options = {}) {
|
|
|
657
646
|
}
|
|
658
647
|
}, [activeIndex, activeId, isTransitioning, enabled, focusDelay]);
|
|
659
648
|
}
|
|
660
|
-
|
|
661
|
-
// hooks/useScrollSystem.ts
|
|
662
|
-
var import_react7 = require("react");
|
|
663
649
|
function useScrollSystem() {
|
|
664
650
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
665
651
|
const globalProgress = useScrollStore((s) => s.globalProgress);
|
|
@@ -679,25 +665,25 @@ function useScrollSystem() {
|
|
|
679
665
|
const activeViewProgress = activeView?.progress ?? 0;
|
|
680
666
|
const canNavigateNext = useScrollStore(selectCanNavigateNext);
|
|
681
667
|
const canNavigatePrevious = useScrollStore(selectCanNavigatePrevious);
|
|
682
|
-
const goToNext =
|
|
668
|
+
const goToNext = react.useCallback(() => {
|
|
683
669
|
if (canNavigateNext) {
|
|
684
670
|
storeNext();
|
|
685
671
|
return true;
|
|
686
672
|
}
|
|
687
673
|
return false;
|
|
688
674
|
}, [canNavigateNext, storeNext]);
|
|
689
|
-
const goToPrev =
|
|
675
|
+
const goToPrev = react.useCallback(() => {
|
|
690
676
|
if (canNavigatePrevious) {
|
|
691
677
|
storePrev();
|
|
692
678
|
return true;
|
|
693
679
|
}
|
|
694
680
|
return false;
|
|
695
681
|
}, [canNavigatePrevious, storePrev]);
|
|
696
|
-
const getCurrentIndex =
|
|
697
|
-
const getProgress =
|
|
698
|
-
const getActiveViewProgress =
|
|
699
|
-
const isLocked =
|
|
700
|
-
return
|
|
682
|
+
const getCurrentIndex = react.useCallback(() => activeIndex, [activeIndex]);
|
|
683
|
+
const getProgress = react.useCallback(() => globalProgress, [globalProgress]);
|
|
684
|
+
const getActiveViewProgress = react.useCallback(() => activeViewProgress, [activeViewProgress]);
|
|
685
|
+
const isLocked = react.useCallback(() => isGlobalLocked || isTransitioning || !canNavigateNext, [isGlobalLocked, isTransitioning, canNavigateNext]);
|
|
686
|
+
return react.useMemo(() => ({
|
|
701
687
|
goToNext,
|
|
702
688
|
goToPrev,
|
|
703
689
|
goTo: storeGoTo,
|
|
@@ -733,6 +719,255 @@ function useScrollSystem() {
|
|
|
733
719
|
isTransitioning
|
|
734
720
|
]);
|
|
735
721
|
}
|
|
722
|
+
function useGlobalProgress(options = {}) {
|
|
723
|
+
const { onProgress, throttle: throttle2 = 16 } = options;
|
|
724
|
+
const progress = useScrollStore(selectGlobalProgress);
|
|
725
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
726
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
727
|
+
react.useEffect(() => {
|
|
728
|
+
if (!onProgress) return;
|
|
729
|
+
let lastCall = 0;
|
|
730
|
+
const now = Date.now();
|
|
731
|
+
if (now - lastCall >= throttle2) {
|
|
732
|
+
lastCall = now;
|
|
733
|
+
onProgress(progress);
|
|
734
|
+
}
|
|
735
|
+
}, [progress, onProgress, throttle2]);
|
|
736
|
+
return {
|
|
737
|
+
progress,
|
|
738
|
+
activeIndex,
|
|
739
|
+
totalViews,
|
|
740
|
+
percentage: Math.round(progress * 100)
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
var DEFAULT_CONFIG = {
|
|
744
|
+
enabled: false,
|
|
745
|
+
interval: 5e3,
|
|
746
|
+
pauseOnInteraction: true,
|
|
747
|
+
resumeDelay: 3e3,
|
|
748
|
+
direction: "forward",
|
|
749
|
+
stopAtEnd: false
|
|
750
|
+
};
|
|
751
|
+
function useAutoScroll(config) {
|
|
752
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
753
|
+
const {
|
|
754
|
+
enabled,
|
|
755
|
+
interval,
|
|
756
|
+
pauseOnInteraction,
|
|
757
|
+
resumeDelay,
|
|
758
|
+
direction,
|
|
759
|
+
stopAtEnd
|
|
760
|
+
} = mergedConfig;
|
|
761
|
+
const intervalRef = react.useRef(null);
|
|
762
|
+
const resumeTimeoutRef = react.useRef(null);
|
|
763
|
+
const isAutoScrolling = useScrollStore((s) => s.isAutoScrolling);
|
|
764
|
+
const isAutoScrollPaused = useScrollStore((s) => s.isAutoScrollPaused);
|
|
765
|
+
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
766
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
767
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
768
|
+
const infiniteScrollEnabled = useScrollStore((s) => s.infiniteScrollEnabled);
|
|
769
|
+
const isDragging = useScrollStore((s) => s.isDragging);
|
|
770
|
+
const setAutoScrolling = useScrollStore((s) => s.setAutoScrolling);
|
|
771
|
+
const setAutoScrollPaused = useScrollStore((s) => s.setAutoScrollPaused);
|
|
772
|
+
const goToNext = useScrollStore((s) => s.goToNext);
|
|
773
|
+
const goToPrevious = useScrollStore((s) => s.goToPrevious);
|
|
774
|
+
const pause = react.useCallback(() => {
|
|
775
|
+
setAutoScrollPaused(true);
|
|
776
|
+
if (resumeTimeoutRef.current) {
|
|
777
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
778
|
+
resumeTimeoutRef.current = null;
|
|
779
|
+
}
|
|
780
|
+
}, [setAutoScrollPaused]);
|
|
781
|
+
const resume = react.useCallback(() => {
|
|
782
|
+
setAutoScrollPaused(false);
|
|
783
|
+
}, [setAutoScrollPaused]);
|
|
784
|
+
const toggle = react.useCallback(() => {
|
|
785
|
+
if (isAutoScrollPaused) {
|
|
786
|
+
resume();
|
|
787
|
+
} else {
|
|
788
|
+
pause();
|
|
789
|
+
}
|
|
790
|
+
}, [isAutoScrollPaused, pause, resume]);
|
|
791
|
+
const reset = react.useCallback(() => {
|
|
792
|
+
if (intervalRef.current) {
|
|
793
|
+
clearInterval(intervalRef.current);
|
|
794
|
+
intervalRef.current = null;
|
|
795
|
+
}
|
|
796
|
+
}, []);
|
|
797
|
+
react.useEffect(() => {
|
|
798
|
+
if (!enabled || !pauseOnInteraction) return;
|
|
799
|
+
if (isDragging || isTransitioning) {
|
|
800
|
+
pause();
|
|
801
|
+
if (resumeTimeoutRef.current) {
|
|
802
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
803
|
+
}
|
|
804
|
+
resumeTimeoutRef.current = setTimeout(() => {
|
|
805
|
+
if (enabled) {
|
|
806
|
+
resume();
|
|
807
|
+
}
|
|
808
|
+
}, resumeDelay);
|
|
809
|
+
}
|
|
810
|
+
return () => {
|
|
811
|
+
if (resumeTimeoutRef.current) {
|
|
812
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
}, [isDragging, isTransitioning, enabled, pauseOnInteraction, resumeDelay, pause, resume]);
|
|
816
|
+
react.useEffect(() => {
|
|
817
|
+
setAutoScrolling(enabled);
|
|
818
|
+
setAutoScrollPaused(false);
|
|
819
|
+
return () => {
|
|
820
|
+
setAutoScrolling(false);
|
|
821
|
+
};
|
|
822
|
+
}, [enabled, setAutoScrolling, setAutoScrollPaused]);
|
|
823
|
+
react.useEffect(() => {
|
|
824
|
+
if (!enabled || isAutoScrollPaused || isTransitioning) {
|
|
825
|
+
reset();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (stopAtEnd && !infiniteScrollEnabled) {
|
|
829
|
+
if (direction === "forward" && activeIndex >= totalViews - 1) {
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (direction === "backward" && activeIndex <= 0) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
intervalRef.current = setInterval(() => {
|
|
837
|
+
if (direction === "forward") {
|
|
838
|
+
goToNext();
|
|
839
|
+
} else {
|
|
840
|
+
goToPrevious();
|
|
841
|
+
}
|
|
842
|
+
}, interval);
|
|
843
|
+
return () => {
|
|
844
|
+
reset();
|
|
845
|
+
};
|
|
846
|
+
}, [
|
|
847
|
+
enabled,
|
|
848
|
+
isAutoScrollPaused,
|
|
849
|
+
isTransitioning,
|
|
850
|
+
interval,
|
|
851
|
+
direction,
|
|
852
|
+
stopAtEnd,
|
|
853
|
+
activeIndex,
|
|
854
|
+
totalViews,
|
|
855
|
+
infiniteScrollEnabled,
|
|
856
|
+
goToNext,
|
|
857
|
+
goToPrevious,
|
|
858
|
+
reset
|
|
859
|
+
]);
|
|
860
|
+
return {
|
|
861
|
+
isPlaying: isAutoScrolling && !isAutoScrollPaused,
|
|
862
|
+
isPaused: isAutoScrollPaused,
|
|
863
|
+
pause,
|
|
864
|
+
resume,
|
|
865
|
+
toggle,
|
|
866
|
+
reset
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
var DEFAULT_CONFIG2 = {
|
|
870
|
+
enabled: false,
|
|
871
|
+
loopDirection: "both"
|
|
872
|
+
};
|
|
873
|
+
function useInfiniteScroll(config = false) {
|
|
874
|
+
const normalizedConfig = typeof config === "boolean" ? { ...DEFAULT_CONFIG2, enabled: config } : { ...DEFAULT_CONFIG2, ...config };
|
|
875
|
+
const { enabled, loopDirection } = normalizedConfig;
|
|
876
|
+
const infiniteScrollEnabled = useScrollStore((s) => s.infiniteScrollEnabled);
|
|
877
|
+
const setInfiniteScrollEnabled = useScrollStore((s) => s.setInfiniteScrollEnabled);
|
|
878
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
879
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
880
|
+
react.useEffect(() => {
|
|
881
|
+
setInfiniteScrollEnabled(enabled);
|
|
882
|
+
return () => {
|
|
883
|
+
};
|
|
884
|
+
}, [enabled, setInfiniteScrollEnabled]);
|
|
885
|
+
const enable = () => setInfiniteScrollEnabled(true);
|
|
886
|
+
const disable = () => setInfiniteScrollEnabled(false);
|
|
887
|
+
const toggle = () => setInfiniteScrollEnabled(!infiniteScrollEnabled);
|
|
888
|
+
const canLoopForward = infiniteScrollEnabled && (loopDirection === "forward" || loopDirection === "both") && activeIndex === totalViews - 1;
|
|
889
|
+
const canLoopBackward = infiniteScrollEnabled && (loopDirection === "backward" || loopDirection === "both") && activeIndex === 0;
|
|
890
|
+
return {
|
|
891
|
+
isEnabled: infiniteScrollEnabled,
|
|
892
|
+
enable,
|
|
893
|
+
disable,
|
|
894
|
+
toggle,
|
|
895
|
+
canLoopForward,
|
|
896
|
+
canLoopBackward
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
var DEFAULT_CONFIG3 = {
|
|
900
|
+
ahead: 1,
|
|
901
|
+
behind: 1,
|
|
902
|
+
delay: 100
|
|
903
|
+
};
|
|
904
|
+
function usePreload(config = {}) {
|
|
905
|
+
const mergedConfig = { ...DEFAULT_CONFIG3, ...config };
|
|
906
|
+
const { ahead, behind, delay } = mergedConfig;
|
|
907
|
+
const views = useScrollStore((s) => s.views);
|
|
908
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
909
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
910
|
+
const infiniteScrollEnabled = useScrollStore((s) => s.infiniteScrollEnabled);
|
|
911
|
+
const setViewPreloaded = useScrollStore((s) => s.setViewPreloaded);
|
|
912
|
+
const preloadedViewIds = react.useMemo(() => {
|
|
913
|
+
const ids = [];
|
|
914
|
+
for (let i = -behind; i <= ahead; i++) {
|
|
915
|
+
let targetIndex = activeIndex + i;
|
|
916
|
+
if (infiniteScrollEnabled) {
|
|
917
|
+
if (targetIndex < 0) targetIndex = totalViews + targetIndex;
|
|
918
|
+
if (targetIndex >= totalViews) targetIndex = targetIndex - totalViews;
|
|
919
|
+
}
|
|
920
|
+
if (targetIndex >= 0 && targetIndex < totalViews) {
|
|
921
|
+
const view = views[targetIndex];
|
|
922
|
+
if (view) {
|
|
923
|
+
ids.push(view.id);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return ids;
|
|
928
|
+
}, [activeIndex, ahead, behind, views, totalViews, infiniteScrollEnabled]);
|
|
929
|
+
react.useEffect(() => {
|
|
930
|
+
const timer = setTimeout(() => {
|
|
931
|
+
views.forEach((view) => {
|
|
932
|
+
const shouldBePreloaded = preloadedViewIds.includes(view.id);
|
|
933
|
+
if (view.isPreloaded !== shouldBePreloaded) {
|
|
934
|
+
setViewPreloaded(view.id, shouldBePreloaded);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
}, delay);
|
|
938
|
+
return () => clearTimeout(timer);
|
|
939
|
+
}, [preloadedViewIds, views, setViewPreloaded, delay]);
|
|
940
|
+
const shouldPreload = (viewId) => {
|
|
941
|
+
return preloadedViewIds.includes(viewId);
|
|
942
|
+
};
|
|
943
|
+
const isPreloaded = (viewId) => {
|
|
944
|
+
const view = views.find((v) => v.id === viewId);
|
|
945
|
+
return view?.isPreloaded ?? false;
|
|
946
|
+
};
|
|
947
|
+
return {
|
|
948
|
+
shouldPreload,
|
|
949
|
+
preloadedViewIds,
|
|
950
|
+
isPreloaded
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
var DEFAULT_GESTURE_CONFIG = {
|
|
954
|
+
swipeThreshold: 50,
|
|
955
|
+
swipeVelocity: 0.5,
|
|
956
|
+
dragResistance: 0.3,
|
|
957
|
+
enableWheel: true,
|
|
958
|
+
enableTouch: true,
|
|
959
|
+
enableKeyboard: true
|
|
960
|
+
};
|
|
961
|
+
var GestureConfigContext = react.createContext(DEFAULT_GESTURE_CONFIG);
|
|
962
|
+
function useGestureConfig() {
|
|
963
|
+
return react.useContext(GestureConfigContext);
|
|
964
|
+
}
|
|
965
|
+
function mergeGestureConfig(config) {
|
|
966
|
+
return react.useMemo(() => ({
|
|
967
|
+
...DEFAULT_GESTURE_CONFIG,
|
|
968
|
+
...config
|
|
969
|
+
}), [config]);
|
|
970
|
+
}
|
|
736
971
|
|
|
737
972
|
// utils/index.ts
|
|
738
973
|
function throttle(fn, limit) {
|
|
@@ -761,9 +996,6 @@ function prefersReducedMotion() {
|
|
|
761
996
|
if (typeof window === "undefined") return false;
|
|
762
997
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
763
998
|
}
|
|
764
|
-
|
|
765
|
-
// components/ScrollContainer.tsx
|
|
766
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
767
999
|
function ScrollContainer({
|
|
768
1000
|
children,
|
|
769
1001
|
className = "",
|
|
@@ -781,12 +1013,20 @@ function ScrollContainer({
|
|
|
781
1013
|
// Touch Physics
|
|
782
1014
|
enableDragPhysics = false,
|
|
783
1015
|
// Layout
|
|
784
|
-
orientation = "vertical"
|
|
1016
|
+
orientation = "vertical",
|
|
1017
|
+
// NEW: v1.1.0 Features
|
|
1018
|
+
skipInitialAnimation = false,
|
|
1019
|
+
onProgress,
|
|
1020
|
+
gestureConfig,
|
|
1021
|
+
autoScroll,
|
|
1022
|
+
infiniteScroll = false,
|
|
1023
|
+
preload = true
|
|
785
1024
|
}) {
|
|
786
|
-
const containerRef =
|
|
1025
|
+
const containerRef = react.useRef(null);
|
|
1026
|
+
const isFirstRender = react.useRef(true);
|
|
787
1027
|
const isBrowser = typeof window !== "undefined";
|
|
788
|
-
const [reducedMotion, setReducedMotion] =
|
|
789
|
-
|
|
1028
|
+
const [reducedMotion, setReducedMotion] = react.useState(false);
|
|
1029
|
+
react.useEffect(() => {
|
|
790
1030
|
if (!isBrowser || !respectReducedMotion) return;
|
|
791
1031
|
setReducedMotion(prefersReducedMotion());
|
|
792
1032
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
@@ -797,9 +1037,9 @@ function ScrollContainer({
|
|
|
797
1037
|
const effectiveDuration = reducedMotion ? 0 : transitionDuration;
|
|
798
1038
|
const { initialize, endTransition } = useScrollStore();
|
|
799
1039
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const prevIndexRef =
|
|
1040
|
+
useScrollStore((s) => s.totalViews);
|
|
1041
|
+
useScrollStore((s) => s.isInitialized);
|
|
1042
|
+
const prevIndexRef = react.useRef(activeIndex);
|
|
803
1043
|
useWheelHandler();
|
|
804
1044
|
useTouchHandler({ enabled: !enableDragPhysics });
|
|
805
1045
|
useKeyboardHandler();
|
|
@@ -812,14 +1052,39 @@ function ScrollContainer({
|
|
|
812
1052
|
hashPrefix
|
|
813
1053
|
});
|
|
814
1054
|
useFocusManagement({ enabled: enableFocusManagement });
|
|
815
|
-
(
|
|
1055
|
+
useGlobalProgress({ onProgress });
|
|
1056
|
+
useInfiniteScroll(infiniteScroll);
|
|
1057
|
+
const autoScrollState = useAutoScroll(
|
|
1058
|
+
autoScroll ?? { enabled: false, interval: 5e3 }
|
|
1059
|
+
);
|
|
1060
|
+
const preloadConfig = typeof preload === "boolean" ? preload ? { ahead: 1, behind: 1 } : void 0 : preload;
|
|
1061
|
+
usePreload(preloadConfig ?? {});
|
|
1062
|
+
const mergedGestureConfig = mergeGestureConfig(gestureConfig);
|
|
1063
|
+
react.useEffect(() => {
|
|
816
1064
|
const timer = setTimeout(() => {
|
|
817
1065
|
initialize();
|
|
818
1066
|
onInitialized?.();
|
|
1067
|
+
isFirstRender.current = false;
|
|
819
1068
|
}, 50);
|
|
820
1069
|
return () => clearTimeout(timer);
|
|
821
1070
|
}, [initialize, onInitialized]);
|
|
822
|
-
|
|
1071
|
+
react.useEffect(() => {
|
|
1072
|
+
if (!isBrowser) return;
|
|
1073
|
+
const html = document.documentElement;
|
|
1074
|
+
const body = document.body;
|
|
1075
|
+
const originalHtmlOverscroll = html.style.overscrollBehavior;
|
|
1076
|
+
const originalBodyOverscroll = body.style.overscrollBehavior;
|
|
1077
|
+
const originalTouchAction = body.style.touchAction;
|
|
1078
|
+
html.style.overscrollBehavior = "none";
|
|
1079
|
+
body.style.overscrollBehavior = "none";
|
|
1080
|
+
body.style.touchAction = "pan-x pan-y";
|
|
1081
|
+
return () => {
|
|
1082
|
+
html.style.overscrollBehavior = originalHtmlOverscroll;
|
|
1083
|
+
body.style.overscrollBehavior = originalBodyOverscroll;
|
|
1084
|
+
body.style.touchAction = originalTouchAction;
|
|
1085
|
+
};
|
|
1086
|
+
}, [isBrowser]);
|
|
1087
|
+
react.useEffect(() => {
|
|
823
1088
|
if (prevIndexRef.current !== activeIndex) {
|
|
824
1089
|
onViewChange?.(prevIndexRef.current, activeIndex);
|
|
825
1090
|
const timer = setTimeout(() => {
|
|
@@ -829,37 +1094,34 @@ function ScrollContainer({
|
|
|
829
1094
|
return () => clearTimeout(timer);
|
|
830
1095
|
}
|
|
831
1096
|
}, [activeIndex, effectiveDuration, onViewChange, endTransition]);
|
|
832
|
-
const wrapperStyle =
|
|
1097
|
+
const wrapperStyle = react.useMemo(() => {
|
|
833
1098
|
const baseOffset = activeIndex * 100;
|
|
834
1099
|
const dragOffset = dragState.isDragging ? dragState.dragOffset * 100 : 0;
|
|
835
1100
|
const transformAxis = orientation === "horizontal" ? "X" : "Y";
|
|
836
1101
|
const sizeUnit = orientation === "horizontal" ? "vw" : "vh";
|
|
1102
|
+
const shouldAnimate = !skipInitialAnimation || !isFirstRender.current;
|
|
1103
|
+
const transitionValue = dragState.isDragging ? "none" : shouldAnimate && effectiveDuration > 0 ? `transform ${effectiveDuration}ms ${transitionEasing}` : "none";
|
|
837
1104
|
return {
|
|
838
1105
|
transform: `translate${transformAxis}(-${baseOffset + dragOffset}${sizeUnit})`,
|
|
839
|
-
transition:
|
|
1106
|
+
transition: transitionValue,
|
|
840
1107
|
height: "100%",
|
|
841
1108
|
width: "100%",
|
|
842
1109
|
display: orientation === "horizontal" ? "flex" : "block",
|
|
843
1110
|
flexDirection: orientation === "horizontal" ? "row" : void 0
|
|
844
1111
|
};
|
|
845
|
-
}, [activeIndex, effectiveDuration, transitionEasing, dragState, orientation]);
|
|
846
|
-
return /* @__PURE__ */ (
|
|
1112
|
+
}, [activeIndex, effectiveDuration, transitionEasing, dragState, orientation, skipInitialAnimation]);
|
|
1113
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GestureConfigContext.Provider, { value: mergedGestureConfig, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
847
1114
|
"div",
|
|
848
1115
|
{
|
|
849
1116
|
ref: containerRef,
|
|
850
1117
|
className: `scroll-container fixed inset-0 overflow-hidden w-screen h-screen ${className}`,
|
|
851
1118
|
role: "main",
|
|
852
1119
|
"aria-label": "Scroll container",
|
|
853
|
-
|
|
1120
|
+
"data-auto-scrolling": autoScrollState.isPlaying,
|
|
1121
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "scroll-wrapper", style: wrapperStyle, children })
|
|
854
1122
|
}
|
|
855
|
-
);
|
|
1123
|
+
) });
|
|
856
1124
|
}
|
|
857
|
-
|
|
858
|
-
// components/FullView.tsx
|
|
859
|
-
var import_react10 = require("react");
|
|
860
|
-
|
|
861
|
-
// hooks/useViewRegistration.ts
|
|
862
|
-
var import_react9 = require("react");
|
|
863
1125
|
function useViewRegistration({
|
|
864
1126
|
config,
|
|
865
1127
|
onActivate,
|
|
@@ -873,7 +1135,7 @@ function useViewRegistration({
|
|
|
873
1135
|
const unregisterView = useScrollStore((s) => s.unregisterView);
|
|
874
1136
|
const activeId = useScrollStore((s) => s.activeId);
|
|
875
1137
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
876
|
-
const callbacksRef =
|
|
1138
|
+
const callbacksRef = react.useRef({
|
|
877
1139
|
onActivate,
|
|
878
1140
|
onDeactivate,
|
|
879
1141
|
onEnterStart,
|
|
@@ -889,20 +1151,20 @@ function useViewRegistration({
|
|
|
889
1151
|
onExitStart,
|
|
890
1152
|
onExitEnd
|
|
891
1153
|
};
|
|
892
|
-
const wasActiveRef =
|
|
893
|
-
const wasTransitioningRef =
|
|
894
|
-
const configRef =
|
|
1154
|
+
const wasActiveRef = react.useRef(false);
|
|
1155
|
+
const wasTransitioningRef = react.useRef(false);
|
|
1156
|
+
const configRef = react.useRef(config);
|
|
895
1157
|
if (JSON.stringify(config) !== JSON.stringify(configRef.current)) {
|
|
896
1158
|
configRef.current = config;
|
|
897
1159
|
}
|
|
898
|
-
|
|
1160
|
+
react.useEffect(() => {
|
|
899
1161
|
const currentConfig = configRef.current;
|
|
900
1162
|
registerView(currentConfig);
|
|
901
1163
|
return () => unregisterView(currentConfig.id);
|
|
902
1164
|
}, [registerView, unregisterView, configRef.current]);
|
|
903
1165
|
const viewState = useScrollStore((s) => s.views.find((v) => v.id === config.id));
|
|
904
1166
|
const isActive = activeId === config.id;
|
|
905
|
-
|
|
1167
|
+
react.useEffect(() => {
|
|
906
1168
|
if (isActive && !wasActiveRef.current) {
|
|
907
1169
|
callbacksRef.current.onActivate?.();
|
|
908
1170
|
} else if (!isActive && wasActiveRef.current) {
|
|
@@ -910,7 +1172,7 @@ function useViewRegistration({
|
|
|
910
1172
|
}
|
|
911
1173
|
wasActiveRef.current = isActive;
|
|
912
1174
|
}, [isActive]);
|
|
913
|
-
|
|
1175
|
+
react.useEffect(() => {
|
|
914
1176
|
const callbacks = callbacksRef.current;
|
|
915
1177
|
const wasActive = wasActiveRef.current;
|
|
916
1178
|
const wasTransitioning = wasTransitioningRef.current;
|
|
@@ -938,9 +1200,6 @@ function useViewRegistration({
|
|
|
938
1200
|
navigation: viewState?.navigation ?? "unlocked"
|
|
939
1201
|
};
|
|
940
1202
|
}
|
|
941
|
-
|
|
942
|
-
// components/FullView.tsx
|
|
943
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
944
1203
|
function FullView({
|
|
945
1204
|
id,
|
|
946
1205
|
children,
|
|
@@ -953,7 +1212,7 @@ function FullView({
|
|
|
953
1212
|
onExitStart,
|
|
954
1213
|
onExitEnd
|
|
955
1214
|
}) {
|
|
956
|
-
const config =
|
|
1215
|
+
const config = react.useMemo(
|
|
957
1216
|
() => ({
|
|
958
1217
|
id,
|
|
959
1218
|
type: "full",
|
|
@@ -970,7 +1229,7 @@ function FullView({
|
|
|
970
1229
|
onExitStart,
|
|
971
1230
|
onExitEnd
|
|
972
1231
|
});
|
|
973
|
-
return /* @__PURE__ */
|
|
1232
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
974
1233
|
"section",
|
|
975
1234
|
{
|
|
976
1235
|
id,
|
|
@@ -990,9 +1249,6 @@ function FullView({
|
|
|
990
1249
|
}
|
|
991
1250
|
);
|
|
992
1251
|
}
|
|
993
|
-
|
|
994
|
-
// hooks/useMetricsReporter.ts
|
|
995
|
-
var import_react11 = require("react");
|
|
996
1252
|
var SCROLL_THROTTLE_MS = 66;
|
|
997
1253
|
function useMetricsReporter({
|
|
998
1254
|
id,
|
|
@@ -1001,9 +1257,9 @@ function useMetricsReporter({
|
|
|
1001
1257
|
onScrollProgress,
|
|
1002
1258
|
throttleMs = SCROLL_THROTTLE_MS
|
|
1003
1259
|
}) {
|
|
1004
|
-
const scrollRef =
|
|
1260
|
+
const scrollRef = react.useRef(null);
|
|
1005
1261
|
const updateMetrics = useScrollStore((s) => s.updateViewMetrics);
|
|
1006
|
-
const measureAndReport =
|
|
1262
|
+
const measureAndReport = react.useCallback(() => {
|
|
1007
1263
|
const el = scrollRef.current;
|
|
1008
1264
|
if (!el) return;
|
|
1009
1265
|
const metrics = {
|
|
@@ -1018,11 +1274,11 @@ function useMetricsReporter({
|
|
|
1018
1274
|
onScrollProgress(progress);
|
|
1019
1275
|
}
|
|
1020
1276
|
}, [id, scrollDirection, updateMetrics, onScrollProgress]);
|
|
1021
|
-
const throttledMeasure =
|
|
1277
|
+
const throttledMeasure = react.useMemo(
|
|
1022
1278
|
() => throttle(measureAndReport, throttleMs),
|
|
1023
1279
|
[measureAndReport, throttleMs]
|
|
1024
1280
|
);
|
|
1025
|
-
|
|
1281
|
+
react.useEffect(() => {
|
|
1026
1282
|
const el = scrollRef.current;
|
|
1027
1283
|
if (!el) return;
|
|
1028
1284
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -1037,16 +1293,13 @@ function useMetricsReporter({
|
|
|
1037
1293
|
el.removeEventListener("scroll", throttledMeasure);
|
|
1038
1294
|
};
|
|
1039
1295
|
}, [measureAndReport, throttledMeasure]);
|
|
1040
|
-
|
|
1296
|
+
react.useEffect(() => {
|
|
1041
1297
|
if (isActive) {
|
|
1042
1298
|
measureAndReport();
|
|
1043
1299
|
}
|
|
1044
1300
|
}, [isActive, measureAndReport]);
|
|
1045
1301
|
return { scrollRef, measureAndReport };
|
|
1046
1302
|
}
|
|
1047
|
-
|
|
1048
|
-
// components/ScrollLockedView.tsx
|
|
1049
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1050
1303
|
function ScrollLockedView({
|
|
1051
1304
|
id,
|
|
1052
1305
|
children,
|
|
@@ -1082,14 +1335,14 @@ function ScrollLockedView({
|
|
|
1082
1335
|
onScrollProgress
|
|
1083
1336
|
});
|
|
1084
1337
|
const scrollClasses = scrollDirection === "vertical" ? "overflow-y-auto overflow-x-hidden" : "overflow-x-auto overflow-y-hidden";
|
|
1085
|
-
return /* @__PURE__ */
|
|
1338
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1086
1339
|
"section",
|
|
1087
1340
|
{
|
|
1088
1341
|
id,
|
|
1089
1342
|
className: `relative w-full h-screen ${className}`,
|
|
1090
1343
|
"data-view-type": "scroll-locked",
|
|
1091
1344
|
"data-active": isActive,
|
|
1092
|
-
children: /* @__PURE__ */
|
|
1345
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1093
1346
|
"div",
|
|
1094
1347
|
{
|
|
1095
1348
|
ref: scrollRef,
|
|
@@ -1101,10 +1354,6 @@ function ScrollLockedView({
|
|
|
1101
1354
|
}
|
|
1102
1355
|
);
|
|
1103
1356
|
}
|
|
1104
|
-
|
|
1105
|
-
// components/ControlledView.tsx
|
|
1106
|
-
var import_react12 = require("react");
|
|
1107
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1108
1357
|
function ControlledView({
|
|
1109
1358
|
id,
|
|
1110
1359
|
children,
|
|
@@ -1121,7 +1370,7 @@ function ControlledView({
|
|
|
1121
1370
|
onExitEnd
|
|
1122
1371
|
}) {
|
|
1123
1372
|
const setExplicitLock = useScrollStore((s) => s.setViewExplicitLock);
|
|
1124
|
-
const config =
|
|
1373
|
+
const config = react.useMemo(
|
|
1125
1374
|
() => ({
|
|
1126
1375
|
id,
|
|
1127
1376
|
type: "controlled",
|
|
@@ -1145,11 +1394,11 @@ function ControlledView({
|
|
|
1145
1394
|
isActive,
|
|
1146
1395
|
scrollDirection: allowInternalScroll ? scrollDirection : "none"
|
|
1147
1396
|
});
|
|
1148
|
-
|
|
1397
|
+
react.useEffect(() => {
|
|
1149
1398
|
const lockState = canProceed ? "unlocked" : "locked";
|
|
1150
1399
|
setExplicitLock(id, lockState);
|
|
1151
1400
|
}, [id, canProceed, setExplicitLock]);
|
|
1152
|
-
const overflowClasses =
|
|
1401
|
+
const overflowClasses = react.useMemo(() => {
|
|
1153
1402
|
if (!allowInternalScroll) return "overflow-hidden";
|
|
1154
1403
|
switch (scrollDirection) {
|
|
1155
1404
|
case "vertical":
|
|
@@ -1160,14 +1409,14 @@ function ControlledView({
|
|
|
1160
1409
|
return "overflow-auto";
|
|
1161
1410
|
}
|
|
1162
1411
|
}, [allowInternalScroll, scrollDirection]);
|
|
1163
|
-
return /* @__PURE__ */
|
|
1412
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1164
1413
|
"section",
|
|
1165
1414
|
{
|
|
1166
1415
|
id,
|
|
1167
1416
|
className: `relative w-full h-screen ${isActive ? "z-10" : "z-0"} ${className}`,
|
|
1168
1417
|
"data-view-type": "controlled",
|
|
1169
1418
|
"data-active": isActive,
|
|
1170
|
-
children: /* @__PURE__ */
|
|
1419
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1171
1420
|
"div",
|
|
1172
1421
|
{
|
|
1173
1422
|
ref: scrollRef,
|
|
@@ -1183,7 +1432,7 @@ function useViewControl(viewId) {
|
|
|
1183
1432
|
const goToNext = useScrollStore((s) => s.goToNext);
|
|
1184
1433
|
const goToPrevious = useScrollStore((s) => s.goToPrevious);
|
|
1185
1434
|
const goToView = useScrollStore((s) => s.goToView);
|
|
1186
|
-
return
|
|
1435
|
+
return react.useMemo(
|
|
1187
1436
|
() => ({
|
|
1188
1437
|
unlock: () => setExplicitLock(viewId, "unlocked"),
|
|
1189
1438
|
lock: () => setExplicitLock(viewId, "locked"),
|
|
@@ -1195,9 +1444,6 @@ function useViewControl(viewId) {
|
|
|
1195
1444
|
[viewId, setExplicitLock, goToNext, goToPrevious, goToView]
|
|
1196
1445
|
);
|
|
1197
1446
|
}
|
|
1198
|
-
|
|
1199
|
-
// components/ScrollDebugOverlay.tsx
|
|
1200
|
-
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1201
1447
|
function ScrollDebugOverlay({
|
|
1202
1448
|
position = "bottom-left",
|
|
1203
1449
|
visible = true
|
|
@@ -1216,36 +1462,36 @@ function ScrollDebugOverlay({
|
|
|
1216
1462
|
"bottom-left": "bottom-4 left-4",
|
|
1217
1463
|
"bottom-right": "bottom-4 right-4"
|
|
1218
1464
|
};
|
|
1219
|
-
return /* @__PURE__ */
|
|
1465
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1220
1466
|
"div",
|
|
1221
1467
|
{
|
|
1222
1468
|
className: `fixed ${positionClasses[position]} z-9999 font-mono text-xs bg-black/90 text-green-400 p-3 rounded-lg border border-green-500/30 backdrop-blur-sm max-w-xs`,
|
|
1223
1469
|
style: { pointerEvents: "none" },
|
|
1224
1470
|
children: [
|
|
1225
|
-
/* @__PURE__ */
|
|
1226
|
-
/* @__PURE__ */
|
|
1471
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-green-300 font-bold mb-2 flex items-center gap-2", children: [
|
|
1472
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2 h-2 rounded-full bg-green-500 animate-pulse" }),
|
|
1227
1473
|
"ScrollSystem Debug"
|
|
1228
1474
|
] }),
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
/* @__PURE__ */
|
|
1475
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 mb-3", children: [
|
|
1476
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "initialized", value: isInitialized }),
|
|
1477
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "activeIndex", value: `${activeIndex} / ${totalViews - 1}` }),
|
|
1478
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "transitioning", value: isTransitioning }),
|
|
1479
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "globalLocked", value: isGlobalLocked })
|
|
1234
1480
|
] }),
|
|
1235
|
-
activeView && /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1238
|
-
/* @__PURE__ */
|
|
1239
|
-
/* @__PURE__ */
|
|
1240
|
-
/* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
1242
|
-
activeView.explicitLock && /* @__PURE__ */
|
|
1481
|
+
activeView && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
|
|
1482
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-green-300/70 mb-1", children: "Active View" }),
|
|
1483
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "id", value: activeView.id }),
|
|
1484
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "type", value: activeView.type }),
|
|
1485
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "capability", value: activeView.capability }),
|
|
1486
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "navigation", value: activeView.navigation }),
|
|
1487
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "progress", value: `${(activeView.progress * 100).toFixed(0)}%` }),
|
|
1488
|
+
activeView.explicitLock && /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "explicitLock", value: activeView.explicitLock, highlight: true })
|
|
1243
1489
|
] }),
|
|
1244
|
-
activeView?.metrics && /* @__PURE__ */
|
|
1245
|
-
/* @__PURE__ */
|
|
1246
|
-
/* @__PURE__ */
|
|
1247
|
-
/* @__PURE__ */
|
|
1248
|
-
/* @__PURE__ */
|
|
1490
|
+
activeView?.metrics && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
|
|
1491
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-green-300/70 mb-1", children: "Metrics" }),
|
|
1492
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "scrollHeight", value: activeView.metrics.scrollHeight }),
|
|
1493
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "clientHeight", value: activeView.metrics.clientHeight }),
|
|
1494
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "scrollTop", value: Math.round(activeView.metrics.scrollTop) })
|
|
1249
1495
|
] })
|
|
1250
1496
|
]
|
|
1251
1497
|
}
|
|
@@ -1258,28 +1504,24 @@ function Row({
|
|
|
1258
1504
|
}) {
|
|
1259
1505
|
const displayValue = typeof value === "boolean" ? value ? "\u2713" : "\u2717" : String(value);
|
|
1260
1506
|
const valueColor = typeof value === "boolean" ? value ? "text-green-400" : "text-red-400" : highlight ? "text-yellow-400" : "text-white";
|
|
1261
|
-
return /* @__PURE__ */
|
|
1262
|
-
/* @__PURE__ */
|
|
1507
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between gap-4", children: [
|
|
1508
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-green-500/70", children: [
|
|
1263
1509
|
label,
|
|
1264
1510
|
":"
|
|
1265
1511
|
] }),
|
|
1266
|
-
/* @__PURE__ */
|
|
1512
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: valueColor, children: displayValue })
|
|
1267
1513
|
] });
|
|
1268
1514
|
}
|
|
1269
|
-
|
|
1270
|
-
// components/AriaLiveRegion.tsx
|
|
1271
|
-
var import_react13 = require("react");
|
|
1272
|
-
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1273
1515
|
function AriaLiveRegion({
|
|
1274
1516
|
template = "Navigated to section {viewIndex} of {totalViews}",
|
|
1275
1517
|
politeness = "polite"
|
|
1276
1518
|
}) {
|
|
1277
|
-
const [announcement, setAnnouncement] =
|
|
1519
|
+
const [announcement, setAnnouncement] = react.useState("");
|
|
1278
1520
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1279
1521
|
const activeId = useScrollStore((s) => s.activeId);
|
|
1280
1522
|
const totalViews = useScrollStore((s) => s.totalViews);
|
|
1281
1523
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
1282
|
-
|
|
1524
|
+
react.useEffect(() => {
|
|
1283
1525
|
if (!isTransitioning && activeId) {
|
|
1284
1526
|
const message = template.replace("{viewIndex}", String(activeIndex + 1)).replace("{viewId}", activeId).replace("{totalViews}", String(totalViews));
|
|
1285
1527
|
const timer = setTimeout(() => {
|
|
@@ -1288,7 +1530,7 @@ function AriaLiveRegion({
|
|
|
1288
1530
|
return () => clearTimeout(timer);
|
|
1289
1531
|
}
|
|
1290
1532
|
}, [activeIndex, activeId, totalViews, isTransitioning, template]);
|
|
1291
|
-
return /* @__PURE__ */
|
|
1533
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1292
1534
|
"div",
|
|
1293
1535
|
{
|
|
1294
1536
|
role: "status",
|
|
@@ -1310,10 +1552,6 @@ function AriaLiveRegion({
|
|
|
1310
1552
|
}
|
|
1311
1553
|
);
|
|
1312
1554
|
}
|
|
1313
|
-
|
|
1314
|
-
// components/LazyView.tsx
|
|
1315
|
-
var import_react14 = require("react");
|
|
1316
|
-
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1317
1555
|
function LazyView({
|
|
1318
1556
|
viewId,
|
|
1319
1557
|
buffer = 1,
|
|
@@ -1322,7 +1560,7 @@ function LazyView({
|
|
|
1322
1560
|
}) {
|
|
1323
1561
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1324
1562
|
const views = useScrollStore((s) => s.views);
|
|
1325
|
-
const shouldRender =
|
|
1563
|
+
const shouldRender = react.useMemo(() => {
|
|
1326
1564
|
const viewIndex = views.findIndex((v) => v.id === viewId);
|
|
1327
1565
|
if (viewIndex === -1) return false;
|
|
1328
1566
|
const minIndex = Math.max(0, activeIndex - buffer);
|
|
@@ -1330,20 +1568,149 @@ function LazyView({
|
|
|
1330
1568
|
return viewIndex >= minIndex && viewIndex <= maxIndex;
|
|
1331
1569
|
}, [viewId, views, activeIndex, buffer]);
|
|
1332
1570
|
if (!shouldRender) {
|
|
1333
|
-
return /* @__PURE__ */
|
|
1571
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: placeholder });
|
|
1334
1572
|
}
|
|
1335
|
-
return /* @__PURE__ */
|
|
1573
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1574
|
+
}
|
|
1575
|
+
function NestedScrollView({
|
|
1576
|
+
id,
|
|
1577
|
+
children,
|
|
1578
|
+
className = "",
|
|
1579
|
+
nestedDirection = "horizontal",
|
|
1580
|
+
enableSnap = true,
|
|
1581
|
+
onItemChange,
|
|
1582
|
+
onActivate,
|
|
1583
|
+
onDeactivate,
|
|
1584
|
+
onEnterStart,
|
|
1585
|
+
onEnterEnd,
|
|
1586
|
+
onExitStart,
|
|
1587
|
+
onExitEnd
|
|
1588
|
+
}) {
|
|
1589
|
+
const containerRef = react.useRef(null);
|
|
1590
|
+
const nestedContainerRef = react.useRef(null);
|
|
1591
|
+
const [activeNestedIndex, setActiveNestedIndex] = react.useState(0);
|
|
1592
|
+
const [isNestedScrolling, setIsNestedScrolling] = react.useState(false);
|
|
1593
|
+
useScrollStore((s) => s.activeIndex);
|
|
1594
|
+
const views = useScrollStore((s) => s.views);
|
|
1595
|
+
const setGlobalLock = useScrollStore((s) => s.setGlobalLock);
|
|
1596
|
+
const view = views.find((v) => v.id === id);
|
|
1597
|
+
const isActive = view?.isActive ?? false;
|
|
1598
|
+
useViewRegistration({
|
|
1599
|
+
config: {
|
|
1600
|
+
id,
|
|
1601
|
+
type: "nested",
|
|
1602
|
+
nestedConfig: {
|
|
1603
|
+
direction: nestedDirection,
|
|
1604
|
+
enableSnap,
|
|
1605
|
+
onItemChange
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
onActivate,
|
|
1609
|
+
onDeactivate,
|
|
1610
|
+
onEnterStart,
|
|
1611
|
+
onEnterEnd,
|
|
1612
|
+
onExitStart,
|
|
1613
|
+
onExitEnd
|
|
1614
|
+
});
|
|
1615
|
+
react.useEffect(() => {
|
|
1616
|
+
if (isActive) {
|
|
1617
|
+
onActivate?.();
|
|
1618
|
+
} else {
|
|
1619
|
+
onDeactivate?.();
|
|
1620
|
+
}
|
|
1621
|
+
}, [isActive, onActivate, onDeactivate]);
|
|
1622
|
+
const handleNestedScroll = react.useCallback((e) => {
|
|
1623
|
+
if (!nestedContainerRef.current) return;
|
|
1624
|
+
const container = nestedContainerRef.current;
|
|
1625
|
+
const scrollPos = nestedDirection === "horizontal" ? container.scrollLeft : container.scrollTop;
|
|
1626
|
+
const itemSize = nestedDirection === "horizontal" ? container.clientWidth : container.clientHeight;
|
|
1627
|
+
if (itemSize > 0) {
|
|
1628
|
+
const newIndex = Math.round(scrollPos / itemSize);
|
|
1629
|
+
if (newIndex !== activeNestedIndex) {
|
|
1630
|
+
setActiveNestedIndex(newIndex);
|
|
1631
|
+
onItemChange?.(newIndex);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}, [nestedDirection, activeNestedIndex, onItemChange]);
|
|
1635
|
+
const handleTouchStart = react.useCallback((e) => {
|
|
1636
|
+
setIsNestedScrolling(true);
|
|
1637
|
+
setGlobalLock(true);
|
|
1638
|
+
}, [setGlobalLock]);
|
|
1639
|
+
const handleTouchEnd = react.useCallback(() => {
|
|
1640
|
+
setTimeout(() => {
|
|
1641
|
+
setIsNestedScrolling(false);
|
|
1642
|
+
setGlobalLock(false);
|
|
1643
|
+
}, 100);
|
|
1644
|
+
}, [setGlobalLock]);
|
|
1645
|
+
react.useCallback((index) => {
|
|
1646
|
+
if (!nestedContainerRef.current) return;
|
|
1647
|
+
const container = nestedContainerRef.current;
|
|
1648
|
+
const itemSize = nestedDirection === "horizontal" ? container.clientWidth : container.clientHeight;
|
|
1649
|
+
const scrollPos = index * itemSize;
|
|
1650
|
+
container.scrollTo({
|
|
1651
|
+
[nestedDirection === "horizontal" ? "left" : "top"]: scrollPos,
|
|
1652
|
+
behavior: "smooth"
|
|
1653
|
+
});
|
|
1654
|
+
}, [nestedDirection]);
|
|
1655
|
+
const nestedScrollStyle = {
|
|
1656
|
+
display: "flex",
|
|
1657
|
+
flexDirection: nestedDirection === "horizontal" ? "row" : "column",
|
|
1658
|
+
overflow: nestedDirection === "horizontal" ? "auto hidden" : "hidden auto",
|
|
1659
|
+
scrollSnapType: enableSnap ? `${nestedDirection === "horizontal" ? "x" : "y"} mandatory` : "none",
|
|
1660
|
+
scrollBehavior: "smooth",
|
|
1661
|
+
WebkitOverflowScrolling: "touch",
|
|
1662
|
+
width: "100%",
|
|
1663
|
+
height: "100%"
|
|
1664
|
+
};
|
|
1665
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1666
|
+
"div",
|
|
1667
|
+
{
|
|
1668
|
+
ref: containerRef,
|
|
1669
|
+
className: `scroll-view nested-scroll-view h-screen w-screen flex-shrink-0 relative ${className}`,
|
|
1670
|
+
"data-view-id": id,
|
|
1671
|
+
"data-view-type": "nested",
|
|
1672
|
+
"data-nested-direction": nestedDirection,
|
|
1673
|
+
role: "region",
|
|
1674
|
+
"aria-label": `Nested scroll view ${id}`,
|
|
1675
|
+
tabIndex: 0,
|
|
1676
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1677
|
+
"div",
|
|
1678
|
+
{
|
|
1679
|
+
ref: nestedContainerRef,
|
|
1680
|
+
className: "nested-scroll-container",
|
|
1681
|
+
style: nestedScrollStyle,
|
|
1682
|
+
onScroll: handleNestedScroll,
|
|
1683
|
+
onTouchStart: handleTouchStart,
|
|
1684
|
+
onTouchEnd: handleTouchEnd,
|
|
1685
|
+
children
|
|
1686
|
+
}
|
|
1687
|
+
)
|
|
1688
|
+
}
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
function NestedScrollItem({
|
|
1692
|
+
children,
|
|
1693
|
+
className = ""
|
|
1694
|
+
}) {
|
|
1695
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1696
|
+
"div",
|
|
1697
|
+
{
|
|
1698
|
+
className: `nested-scroll-item flex-shrink-0 w-full h-full ${className}`,
|
|
1699
|
+
style: {
|
|
1700
|
+
scrollSnapAlign: "start",
|
|
1701
|
+
scrollSnapStop: "always"
|
|
1702
|
+
},
|
|
1703
|
+
children
|
|
1704
|
+
}
|
|
1705
|
+
);
|
|
1336
1706
|
}
|
|
1337
|
-
|
|
1338
|
-
// hooks/useNavigation.ts
|
|
1339
|
-
var import_react15 = require("react");
|
|
1340
1707
|
function useNavigation() {
|
|
1341
1708
|
const goToView = useScrollStore((s) => s.goToView);
|
|
1342
1709
|
const goToNextAction = useScrollStore((s) => s.goToNext);
|
|
1343
1710
|
const goToPreviousAction = useScrollStore((s) => s.goToPrevious);
|
|
1344
1711
|
const setGlobalLock = useScrollStore((s) => s.setGlobalLock);
|
|
1345
|
-
const lockScroll =
|
|
1346
|
-
const unlockScroll =
|
|
1712
|
+
const lockScroll = react.useCallback(() => setGlobalLock(true), [setGlobalLock]);
|
|
1713
|
+
const unlockScroll = react.useCallback(() => setGlobalLock(false), [setGlobalLock]);
|
|
1347
1714
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1348
1715
|
const activeId = useScrollStore((s) => s.activeId);
|
|
1349
1716
|
const totalViews = useScrollStore((s) => s.totalViews);
|
|
@@ -1351,16 +1718,16 @@ function useNavigation() {
|
|
|
1351
1718
|
const isScrollLocked = useScrollStore((s) => s.isGlobalLocked);
|
|
1352
1719
|
const canNavigateNext = useScrollStore(selectCanNavigateNext);
|
|
1353
1720
|
const canNavigatePrevious = useScrollStore(selectCanNavigatePrevious);
|
|
1354
|
-
const goToNext =
|
|
1721
|
+
const goToNext = react.useCallback(() => {
|
|
1355
1722
|
return goToNextAction();
|
|
1356
1723
|
}, [goToNextAction]);
|
|
1357
|
-
const goToPrevious =
|
|
1724
|
+
const goToPrevious = react.useCallback(() => {
|
|
1358
1725
|
return goToPreviousAction();
|
|
1359
1726
|
}, [goToPreviousAction]);
|
|
1360
1727
|
const isFirstView = activeIndex === 0;
|
|
1361
1728
|
const isLastView = activeIndex === totalViews - 1;
|
|
1362
1729
|
const progress = totalViews > 1 ? activeIndex / (totalViews - 1) : 0;
|
|
1363
|
-
return
|
|
1730
|
+
return react.useMemo(
|
|
1364
1731
|
() => ({
|
|
1365
1732
|
// Acciones de navegación
|
|
1366
1733
|
goToView,
|
|
@@ -1430,18 +1797,15 @@ function useActiveViewProgress() {
|
|
|
1430
1797
|
viewType: activeView?.type
|
|
1431
1798
|
};
|
|
1432
1799
|
}
|
|
1433
|
-
|
|
1434
|
-
// hooks/useScrollAnalytics.ts
|
|
1435
|
-
var import_react16 = require("react");
|
|
1436
1800
|
function useScrollAnalytics(options = {}) {
|
|
1437
1801
|
const { onViewEnter, onViewExit, enabled = true } = options;
|
|
1438
1802
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1439
1803
|
const activeId = useScrollStore((s) => s.activeId);
|
|
1440
1804
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
1441
|
-
const enterTimeRef =
|
|
1442
|
-
const prevIndexRef =
|
|
1443
|
-
const prevIdRef =
|
|
1444
|
-
const createAnalytics =
|
|
1805
|
+
const enterTimeRef = react.useRef(Date.now());
|
|
1806
|
+
const prevIndexRef = react.useRef(activeIndex);
|
|
1807
|
+
const prevIdRef = react.useRef(activeId);
|
|
1808
|
+
const createAnalytics = react.useCallback((viewId, viewIndex, enterTime, isActive, exitTime = null) => ({
|
|
1445
1809
|
viewId,
|
|
1446
1810
|
viewIndex,
|
|
1447
1811
|
enterTime,
|
|
@@ -1449,7 +1813,7 @@ function useScrollAnalytics(options = {}) {
|
|
|
1449
1813
|
duration: exitTime ? (exitTime - enterTime) / 1e3 : 0,
|
|
1450
1814
|
isActive
|
|
1451
1815
|
}), []);
|
|
1452
|
-
|
|
1816
|
+
react.useEffect(() => {
|
|
1453
1817
|
if (!enabled) return;
|
|
1454
1818
|
if (!isTransitioning && (activeIndex !== prevIndexRef.current || activeId !== prevIdRef.current)) {
|
|
1455
1819
|
const now = Date.now();
|
|
@@ -1484,37 +1848,249 @@ function useScrollAnalytics(options = {}) {
|
|
|
1484
1848
|
getTimeInView: () => (Date.now() - enterTimeRef.current) / 1e3
|
|
1485
1849
|
};
|
|
1486
1850
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
}
|
|
1851
|
+
function useScrollLock() {
|
|
1852
|
+
const isLocked = useScrollStore((s) => s.isGlobalLocked);
|
|
1853
|
+
const setGlobalLock = useScrollStore((s) => s.setGlobalLock);
|
|
1854
|
+
const setViewExplicitLock = useScrollStore((s) => s.setViewExplicitLock);
|
|
1855
|
+
const lock = react.useCallback(() => {
|
|
1856
|
+
setGlobalLock(true);
|
|
1857
|
+
}, [setGlobalLock]);
|
|
1858
|
+
const unlock = react.useCallback(() => {
|
|
1859
|
+
setGlobalLock(false);
|
|
1860
|
+
}, [setGlobalLock]);
|
|
1861
|
+
const toggle = react.useCallback(() => {
|
|
1862
|
+
setGlobalLock(!isLocked);
|
|
1863
|
+
}, [setGlobalLock, isLocked]);
|
|
1864
|
+
const lockView = react.useCallback((viewId) => {
|
|
1865
|
+
setViewExplicitLock(viewId, "locked");
|
|
1866
|
+
}, [setViewExplicitLock]);
|
|
1867
|
+
const unlockView = react.useCallback((viewId) => {
|
|
1868
|
+
setViewExplicitLock(viewId, "unlocked");
|
|
1869
|
+
}, [setViewExplicitLock]);
|
|
1870
|
+
return {
|
|
1871
|
+
isLocked,
|
|
1872
|
+
lock,
|
|
1873
|
+
unlock,
|
|
1874
|
+
toggle,
|
|
1875
|
+
lockView,
|
|
1876
|
+
unlockView
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
var DEFAULT_CONFIG4 = {
|
|
1880
|
+
speed: 0.5,
|
|
1881
|
+
direction: "vertical",
|
|
1882
|
+
offset: 0,
|
|
1883
|
+
easing: "linear"
|
|
1884
|
+
};
|
|
1885
|
+
var easingFunctions = {
|
|
1886
|
+
linear: (t) => t,
|
|
1887
|
+
easeOut: (t) => 1 - Math.pow(1 - t, 2),
|
|
1888
|
+
easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
|
|
1889
|
+
};
|
|
1890
|
+
function useParallax(viewId, config = {}) {
|
|
1891
|
+
const mergedConfig = { ...DEFAULT_CONFIG4, ...config };
|
|
1892
|
+
const { speed, direction, offset, easing } = mergedConfig;
|
|
1893
|
+
const views = useScrollStore((s) => s.views);
|
|
1894
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1895
|
+
useScrollStore((s) => s.globalProgress);
|
|
1896
|
+
const result = react.useMemo(() => {
|
|
1897
|
+
const view = views.find((v) => v.id === viewId);
|
|
1898
|
+
if (!view) {
|
|
1899
|
+
return {
|
|
1900
|
+
transform: 0,
|
|
1901
|
+
style: {},
|
|
1902
|
+
progress: 0
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
const viewOffset = view.index - activeIndex;
|
|
1906
|
+
const internalProgress = view.isActive ? view.progress : 0;
|
|
1907
|
+
const combinedProgress = viewOffset + internalProgress;
|
|
1908
|
+
const easingFn = easingFunctions[easing];
|
|
1909
|
+
const easedProgress = easingFn(Math.abs(combinedProgress)) * Math.sign(combinedProgress);
|
|
1910
|
+
const maxDistance = 100;
|
|
1911
|
+
const transformValue = easedProgress * maxDistance * speed + offset;
|
|
1912
|
+
const transformProperty = direction === "vertical" ? `translateY(${transformValue}px)` : `translateX(${transformValue}px)`;
|
|
1913
|
+
const style = {
|
|
1914
|
+
transform: transformProperty,
|
|
1915
|
+
willChange: "transform"
|
|
1916
|
+
};
|
|
1917
|
+
return {
|
|
1918
|
+
transform: transformValue,
|
|
1919
|
+
style,
|
|
1920
|
+
progress: combinedProgress
|
|
1921
|
+
};
|
|
1922
|
+
}, [viewId, views, activeIndex, speed, direction, offset, easing]);
|
|
1923
|
+
return result;
|
|
1924
|
+
}
|
|
1925
|
+
function useActiveParallax(config = {}) {
|
|
1926
|
+
const mergedConfig = { ...DEFAULT_CONFIG4, ...config };
|
|
1927
|
+
const { speed, direction, offset, easing } = mergedConfig;
|
|
1928
|
+
const globalProgress = useScrollStore((s) => s.globalProgress);
|
|
1929
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1930
|
+
const views = useScrollStore((s) => s.views);
|
|
1931
|
+
return react.useMemo(() => {
|
|
1932
|
+
const activeView = views[activeIndex];
|
|
1933
|
+
const viewProgress = activeView?.progress ?? 0;
|
|
1934
|
+
const easingFn = easingFunctions[easing];
|
|
1935
|
+
const easedProgress = easingFn(viewProgress);
|
|
1936
|
+
const maxDistance = 100;
|
|
1937
|
+
const transformValue = easedProgress * maxDistance * speed + offset;
|
|
1938
|
+
const transformProperty = direction === "vertical" ? `translateY(${transformValue}px)` : `translateX(${transformValue}px)`;
|
|
1939
|
+
const style = {
|
|
1940
|
+
transform: transformProperty,
|
|
1941
|
+
willChange: "transform"
|
|
1942
|
+
};
|
|
1943
|
+
return {
|
|
1944
|
+
transform: transformValue,
|
|
1945
|
+
style,
|
|
1946
|
+
progress: viewProgress
|
|
1947
|
+
};
|
|
1948
|
+
}, [globalProgress, activeIndex, views, speed, direction, offset, easing]);
|
|
1949
|
+
}
|
|
1950
|
+
function useSnapPoints(options) {
|
|
1951
|
+
const { viewId, points, snapThreshold = 0.1, smoothScroll = true } = options;
|
|
1952
|
+
const scrollContainerRef = react.useRef(null);
|
|
1953
|
+
const lastActivePointRef = react.useRef(null);
|
|
1954
|
+
const views = useScrollStore((s) => s.views);
|
|
1955
|
+
const setActiveSnapPoint = useScrollStore((s) => s.setActiveSnapPoint);
|
|
1956
|
+
const view = react.useMemo(() => views.find((v) => v.id === viewId), [views, viewId]);
|
|
1957
|
+
const viewProgress = view?.progress ?? 0;
|
|
1958
|
+
view?.activeSnapPointId ?? null;
|
|
1959
|
+
const sortedPoints = react.useMemo(
|
|
1960
|
+
() => [...points].sort((a, b) => a.position - b.position),
|
|
1961
|
+
[points]
|
|
1962
|
+
);
|
|
1963
|
+
const activePointData = react.useMemo(() => {
|
|
1964
|
+
if (sortedPoints.length === 0) {
|
|
1965
|
+
return { point: null, index: -1 };
|
|
1966
|
+
}
|
|
1967
|
+
let closestIndex = 0;
|
|
1968
|
+
let closestDistance = Math.abs(viewProgress - sortedPoints[0].position);
|
|
1969
|
+
for (let i = 1; i < sortedPoints.length; i++) {
|
|
1970
|
+
const distance = Math.abs(viewProgress - sortedPoints[i].position);
|
|
1971
|
+
if (distance < closestDistance) {
|
|
1972
|
+
closestDistance = distance;
|
|
1973
|
+
closestIndex = i;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
if (closestDistance <= snapThreshold) {
|
|
1977
|
+
return { point: sortedPoints[closestIndex], index: closestIndex };
|
|
1978
|
+
}
|
|
1979
|
+
for (let i = sortedPoints.length - 1; i >= 0; i--) {
|
|
1980
|
+
if (viewProgress >= sortedPoints[i].position - snapThreshold) {
|
|
1981
|
+
return { point: sortedPoints[i], index: i };
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
return { point: sortedPoints[0], index: 0 };
|
|
1985
|
+
}, [viewProgress, sortedPoints, snapThreshold]);
|
|
1986
|
+
react.useEffect(() => {
|
|
1987
|
+
const newPointId = activePointData.point?.id ?? null;
|
|
1988
|
+
if (newPointId !== lastActivePointRef.current) {
|
|
1989
|
+
if (lastActivePointRef.current) {
|
|
1990
|
+
const prevPoint = sortedPoints.find((p) => p.id === lastActivePointRef.current);
|
|
1991
|
+
prevPoint?.onLeave?.();
|
|
1992
|
+
}
|
|
1993
|
+
setActiveSnapPoint(viewId, newPointId);
|
|
1994
|
+
if (newPointId) {
|
|
1995
|
+
const newPoint = sortedPoints.find((p) => p.id === newPointId);
|
|
1996
|
+
newPoint?.onReach?.();
|
|
1997
|
+
}
|
|
1998
|
+
lastActivePointRef.current = newPointId;
|
|
1999
|
+
}
|
|
2000
|
+
}, [activePointData.point?.id, viewId, sortedPoints, setActiveSnapPoint]);
|
|
2001
|
+
const goToPoint = react.useCallback((pointId) => {
|
|
2002
|
+
const point = sortedPoints.find((p) => p.id === pointId);
|
|
2003
|
+
if (!point || !scrollContainerRef.current) return;
|
|
2004
|
+
const container = scrollContainerRef.current;
|
|
2005
|
+
const maxScroll = container.scrollHeight - container.clientHeight;
|
|
2006
|
+
const targetScroll = point.position * maxScroll;
|
|
2007
|
+
container.scrollTo({
|
|
2008
|
+
top: targetScroll,
|
|
2009
|
+
behavior: smoothScroll ? "smooth" : "auto"
|
|
2010
|
+
});
|
|
2011
|
+
}, [sortedPoints, smoothScroll]);
|
|
2012
|
+
const goToNextPoint = react.useCallback(() => {
|
|
2013
|
+
const nextIndex = Math.min(activePointData.index + 1, sortedPoints.length - 1);
|
|
2014
|
+
const nextPoint = sortedPoints[nextIndex];
|
|
2015
|
+
if (nextPoint) {
|
|
2016
|
+
goToPoint(nextPoint.id);
|
|
2017
|
+
}
|
|
2018
|
+
}, [activePointData.index, sortedPoints, goToPoint]);
|
|
2019
|
+
const goToPrevPoint = react.useCallback(() => {
|
|
2020
|
+
const prevIndex = Math.max(activePointData.index - 1, 0);
|
|
2021
|
+
const prevPoint = sortedPoints[prevIndex];
|
|
2022
|
+
if (prevPoint) {
|
|
2023
|
+
goToPoint(prevPoint.id);
|
|
2024
|
+
}
|
|
2025
|
+
}, [activePointData.index, sortedPoints, goToPoint]);
|
|
2026
|
+
const pointsWithState = react.useMemo(
|
|
2027
|
+
() => sortedPoints.map((point) => ({
|
|
2028
|
+
...point,
|
|
2029
|
+
isActive: point.id === activePointData.point?.id
|
|
2030
|
+
})),
|
|
2031
|
+
[sortedPoints, activePointData.point?.id]
|
|
2032
|
+
);
|
|
2033
|
+
return {
|
|
2034
|
+
activePoint: activePointData.point,
|
|
2035
|
+
activeIndex: activePointData.index,
|
|
2036
|
+
goToPoint,
|
|
2037
|
+
goToNextPoint,
|
|
2038
|
+
goToPrevPoint,
|
|
2039
|
+
points: pointsWithState,
|
|
2040
|
+
progress: viewProgress
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
function createSnapPoints(positions, options) {
|
|
2044
|
+
const { prefix = "snap", labels } = options ?? {};
|
|
2045
|
+
return positions.map((position, index) => ({
|
|
2046
|
+
id: `${prefix}-${index}`,
|
|
2047
|
+
position,
|
|
2048
|
+
label: labels?.[index] ?? `Section ${index + 1}`
|
|
2049
|
+
}));
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
exports.AriaLiveRegion = AriaLiveRegion;
|
|
2053
|
+
exports.ControlledView = ControlledView;
|
|
2054
|
+
exports.DEFAULT_PROGRESS_DEBOUNCE = DEFAULT_PROGRESS_DEBOUNCE;
|
|
2055
|
+
exports.DEFAULT_TRANSITION_DURATION = DEFAULT_TRANSITION_DURATION;
|
|
2056
|
+
exports.DEFAULT_TRANSITION_EASING = DEFAULT_TRANSITION_EASING;
|
|
2057
|
+
exports.FullView = FullView;
|
|
2058
|
+
exports.LazyView = LazyView;
|
|
2059
|
+
exports.NAVIGATION_COOLDOWN = NAVIGATION_COOLDOWN;
|
|
2060
|
+
exports.NAV_THRESHOLDS = NAV_THRESHOLDS;
|
|
2061
|
+
exports.NestedScrollItem = NestedScrollItem;
|
|
2062
|
+
exports.NestedScrollView = NestedScrollView;
|
|
2063
|
+
exports.ScrollContainer = ScrollContainer;
|
|
2064
|
+
exports.ScrollDebugOverlay = ScrollDebugOverlay;
|
|
2065
|
+
exports.ScrollLockedView = ScrollLockedView;
|
|
2066
|
+
exports.createSnapPoints = createSnapPoints;
|
|
2067
|
+
exports.selectActiveView = selectActiveView;
|
|
2068
|
+
exports.selectActiveViewProgress = selectActiveViewProgress;
|
|
2069
|
+
exports.selectCanNavigateNext = selectCanNavigateNext;
|
|
2070
|
+
exports.selectCanNavigatePrevious = selectCanNavigatePrevious;
|
|
2071
|
+
exports.selectGlobalProgress = selectGlobalProgress;
|
|
2072
|
+
exports.selectIsAutoScrolling = selectIsAutoScrolling;
|
|
2073
|
+
exports.useActiveParallax = useActiveParallax;
|
|
2074
|
+
exports.useActiveViewProgress = useActiveViewProgress;
|
|
2075
|
+
exports.useAutoScroll = useAutoScroll;
|
|
2076
|
+
exports.useDragHandler = useDragHandler;
|
|
2077
|
+
exports.useFocusManagement = useFocusManagement;
|
|
2078
|
+
exports.useGestureConfig = useGestureConfig;
|
|
2079
|
+
exports.useGlobalProgress = useGlobalProgress;
|
|
2080
|
+
exports.useHashSync = useHashSync;
|
|
2081
|
+
exports.useInfiniteScroll = useInfiniteScroll;
|
|
2082
|
+
exports.useKeyboardHandler = useKeyboardHandler;
|
|
2083
|
+
exports.useMetricsReporter = useMetricsReporter;
|
|
2084
|
+
exports.useNavigation = useNavigation;
|
|
2085
|
+
exports.useParallax = useParallax;
|
|
2086
|
+
exports.usePreload = usePreload;
|
|
2087
|
+
exports.useScrollAnalytics = useScrollAnalytics;
|
|
2088
|
+
exports.useScrollLock = useScrollLock;
|
|
2089
|
+
exports.useScrollStore = useScrollStore;
|
|
2090
|
+
exports.useScrollSystem = useScrollSystem;
|
|
2091
|
+
exports.useSnapPoints = useSnapPoints;
|
|
2092
|
+
exports.useTouchHandler = useTouchHandler;
|
|
2093
|
+
exports.useViewControl = useViewControl;
|
|
2094
|
+
exports.useViewProgress = useViewProgress;
|
|
2095
|
+
exports.useViewRegistration = useViewRegistration;
|
|
2096
|
+
exports.useWheelHandler = useWheelHandler;
|