scroll-system 1.0.1 → 1.1.1
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 +878 -284
- package/dist/index.mjs +817 -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) {
|
|
@@ -319,6 +324,24 @@ function useWheelHandler() {
|
|
|
319
324
|
return;
|
|
320
325
|
}
|
|
321
326
|
const normalized = normalizeWheel(event);
|
|
327
|
+
const activeView = state.views[state.activeIndex];
|
|
328
|
+
if (activeView?.capability === "internal") {
|
|
329
|
+
const scrollContainer = document.querySelector(
|
|
330
|
+
`[data-view-type="scroll-locked"][data-active="true"] > div`
|
|
331
|
+
);
|
|
332
|
+
if (scrollContainer) {
|
|
333
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
|
334
|
+
const maxScroll = scrollHeight - clientHeight;
|
|
335
|
+
const isScrollingDown = normalized.pixelY > 0;
|
|
336
|
+
const isScrollingUp = normalized.pixelY < 0;
|
|
337
|
+
const isAtBottom = scrollTop >= maxScroll - 1;
|
|
338
|
+
const isAtTop = scrollTop <= 1;
|
|
339
|
+
if (isScrollingDown && !isAtBottom || isScrollingUp && !isAtTop) {
|
|
340
|
+
scrollAccumulator.current = 0;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
322
345
|
const delta = normalized.pixelY;
|
|
323
346
|
scrollAccumulator.current += delta;
|
|
324
347
|
const now = Date.now();
|
|
@@ -338,7 +361,6 @@ function useWheelHandler() {
|
|
|
338
361
|
if (handled) {
|
|
339
362
|
scrollAccumulator.current = 0;
|
|
340
363
|
event.preventDefault();
|
|
341
|
-
} else {
|
|
342
364
|
}
|
|
343
365
|
}
|
|
344
366
|
};
|
|
@@ -346,14 +368,11 @@ function useWheelHandler() {
|
|
|
346
368
|
return () => window.removeEventListener("wheel", handleWheel);
|
|
347
369
|
}, []);
|
|
348
370
|
}
|
|
349
|
-
|
|
350
|
-
// hooks/useTouchHandler.ts
|
|
351
|
-
var import_react2 = require("react");
|
|
352
371
|
function useTouchHandler(options = {}) {
|
|
353
372
|
const { enabled = true } = options;
|
|
354
|
-
const touchStart =
|
|
355
|
-
const touchStartTime =
|
|
356
|
-
|
|
373
|
+
const touchStart = react.useRef(null);
|
|
374
|
+
const touchStartTime = react.useRef(0);
|
|
375
|
+
react.useEffect(() => {
|
|
357
376
|
if (!enabled) return;
|
|
358
377
|
const handleTouchStart = (e) => {
|
|
359
378
|
touchStart.current = {
|
|
@@ -394,12 +413,9 @@ function useTouchHandler(options = {}) {
|
|
|
394
413
|
};
|
|
395
414
|
}, [enabled]);
|
|
396
415
|
}
|
|
397
|
-
|
|
398
|
-
// hooks/useKeyboardHandler.ts
|
|
399
|
-
var import_react3 = require("react");
|
|
400
416
|
function useKeyboardHandler(options = {}) {
|
|
401
417
|
const { enabled = true, preventDefault = true } = options;
|
|
402
|
-
|
|
418
|
+
react.useEffect(() => {
|
|
403
419
|
if (!enabled) return;
|
|
404
420
|
const handleKeyDown = (e) => {
|
|
405
421
|
const target = e.target;
|
|
@@ -457,13 +473,10 @@ function useKeyboardHandler(options = {}) {
|
|
|
457
473
|
};
|
|
458
474
|
}, [enabled, preventDefault]);
|
|
459
475
|
}
|
|
460
|
-
|
|
461
|
-
// hooks/useHashSync.ts
|
|
462
|
-
var import_react4 = require("react");
|
|
463
476
|
function useHashSync(options = {}) {
|
|
464
477
|
const { enabled = true, pushHistory = false, hashPrefix = "" } = options;
|
|
465
|
-
const hasInitialized =
|
|
466
|
-
|
|
478
|
+
const hasInitialized = react.useRef(false);
|
|
479
|
+
react.useEffect(() => {
|
|
467
480
|
if (!enabled) return;
|
|
468
481
|
const unsubscribe = useScrollStore.subscribe(
|
|
469
482
|
(state) => state.activeIndex,
|
|
@@ -484,7 +497,7 @@ function useHashSync(options = {}) {
|
|
|
484
497
|
);
|
|
485
498
|
return () => unsubscribe();
|
|
486
499
|
}, [enabled, pushHistory, hashPrefix]);
|
|
487
|
-
|
|
500
|
+
react.useEffect(() => {
|
|
488
501
|
if (!enabled) return;
|
|
489
502
|
const handlePopState = () => {
|
|
490
503
|
const hash = window.location.hash.slice(1);
|
|
@@ -499,7 +512,7 @@ function useHashSync(options = {}) {
|
|
|
499
512
|
window.addEventListener("popstate", handlePopState);
|
|
500
513
|
return () => window.removeEventListener("popstate", handlePopState);
|
|
501
514
|
}, [enabled, hashPrefix]);
|
|
502
|
-
|
|
515
|
+
react.useEffect(() => {
|
|
503
516
|
if (!enabled) return;
|
|
504
517
|
const checkAndNavigate = () => {
|
|
505
518
|
const state = useScrollStore.getState();
|
|
@@ -522,30 +535,27 @@ function useHashSync(options = {}) {
|
|
|
522
535
|
checkAndNavigate();
|
|
523
536
|
}, [enabled, hashPrefix]);
|
|
524
537
|
}
|
|
525
|
-
|
|
526
|
-
// hooks/useDragHandler.ts
|
|
527
|
-
var import_react5 = require("react");
|
|
528
538
|
var DRAG_THRESHOLD = 50;
|
|
529
539
|
var VELOCITY_THRESHOLD = 0.5;
|
|
530
540
|
var RESISTANCE_FACTOR = 0.4;
|
|
531
541
|
function useDragHandler(options = {}) {
|
|
532
542
|
const { enabled = true, onDragUpdate, onDragEnd } = options;
|
|
533
|
-
const [dragState, setDragState] =
|
|
543
|
+
const [dragState, setDragState] = react.useState({
|
|
534
544
|
isDragging: false,
|
|
535
545
|
dragOffset: 0,
|
|
536
546
|
dragDirection: null
|
|
537
547
|
});
|
|
538
|
-
const touchStartRef =
|
|
539
|
-
const lastMoveRef =
|
|
540
|
-
const rafRef =
|
|
541
|
-
const updateDragState =
|
|
548
|
+
const touchStartRef = react.useRef(null);
|
|
549
|
+
const lastMoveRef = react.useRef(null);
|
|
550
|
+
const rafRef = react.useRef(null);
|
|
551
|
+
const updateDragState = react.useCallback((newState) => {
|
|
542
552
|
setDragState((prev) => {
|
|
543
553
|
const updated = { ...prev, ...newState };
|
|
544
554
|
onDragUpdate?.(updated);
|
|
545
555
|
return updated;
|
|
546
556
|
});
|
|
547
557
|
}, [onDragUpdate]);
|
|
548
|
-
|
|
558
|
+
react.useEffect(() => {
|
|
549
559
|
if (!enabled) return;
|
|
550
560
|
const handleTouchStart = (e) => {
|
|
551
561
|
const target = e.target;
|
|
@@ -631,16 +641,13 @@ function useDragHandler(options = {}) {
|
|
|
631
641
|
}, [enabled, updateDragState, onDragEnd]);
|
|
632
642
|
return dragState;
|
|
633
643
|
}
|
|
634
|
-
|
|
635
|
-
// hooks/useFocusManagement.ts
|
|
636
|
-
var import_react6 = require("react");
|
|
637
644
|
function useFocusManagement(options = {}) {
|
|
638
645
|
const { enabled = true, focusDelay = 100 } = options;
|
|
639
646
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
640
647
|
const activeId = useScrollStore((s) => s.activeId);
|
|
641
648
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
642
|
-
const prevIndexRef =
|
|
643
|
-
|
|
649
|
+
const prevIndexRef = react.useRef(activeIndex);
|
|
650
|
+
react.useEffect(() => {
|
|
644
651
|
if (!enabled) return;
|
|
645
652
|
if (activeIndex !== prevIndexRef.current && !isTransitioning && activeId) {
|
|
646
653
|
const timer = setTimeout(() => {
|
|
@@ -657,9 +664,6 @@ function useFocusManagement(options = {}) {
|
|
|
657
664
|
}
|
|
658
665
|
}, [activeIndex, activeId, isTransitioning, enabled, focusDelay]);
|
|
659
666
|
}
|
|
660
|
-
|
|
661
|
-
// hooks/useScrollSystem.ts
|
|
662
|
-
var import_react7 = require("react");
|
|
663
667
|
function useScrollSystem() {
|
|
664
668
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
665
669
|
const globalProgress = useScrollStore((s) => s.globalProgress);
|
|
@@ -679,25 +683,25 @@ function useScrollSystem() {
|
|
|
679
683
|
const activeViewProgress = activeView?.progress ?? 0;
|
|
680
684
|
const canNavigateNext = useScrollStore(selectCanNavigateNext);
|
|
681
685
|
const canNavigatePrevious = useScrollStore(selectCanNavigatePrevious);
|
|
682
|
-
const goToNext =
|
|
686
|
+
const goToNext = react.useCallback(() => {
|
|
683
687
|
if (canNavigateNext) {
|
|
684
688
|
storeNext();
|
|
685
689
|
return true;
|
|
686
690
|
}
|
|
687
691
|
return false;
|
|
688
692
|
}, [canNavigateNext, storeNext]);
|
|
689
|
-
const goToPrev =
|
|
693
|
+
const goToPrev = react.useCallback(() => {
|
|
690
694
|
if (canNavigatePrevious) {
|
|
691
695
|
storePrev();
|
|
692
696
|
return true;
|
|
693
697
|
}
|
|
694
698
|
return false;
|
|
695
699
|
}, [canNavigatePrevious, storePrev]);
|
|
696
|
-
const getCurrentIndex =
|
|
697
|
-
const getProgress =
|
|
698
|
-
const getActiveViewProgress =
|
|
699
|
-
const isLocked =
|
|
700
|
-
return
|
|
700
|
+
const getCurrentIndex = react.useCallback(() => activeIndex, [activeIndex]);
|
|
701
|
+
const getProgress = react.useCallback(() => globalProgress, [globalProgress]);
|
|
702
|
+
const getActiveViewProgress = react.useCallback(() => activeViewProgress, [activeViewProgress]);
|
|
703
|
+
const isLocked = react.useCallback(() => isGlobalLocked || isTransitioning || !canNavigateNext, [isGlobalLocked, isTransitioning, canNavigateNext]);
|
|
704
|
+
return react.useMemo(() => ({
|
|
701
705
|
goToNext,
|
|
702
706
|
goToPrev,
|
|
703
707
|
goTo: storeGoTo,
|
|
@@ -733,6 +737,255 @@ function useScrollSystem() {
|
|
|
733
737
|
isTransitioning
|
|
734
738
|
]);
|
|
735
739
|
}
|
|
740
|
+
function useGlobalProgress(options = {}) {
|
|
741
|
+
const { onProgress, throttle: throttle2 = 16 } = options;
|
|
742
|
+
const progress = useScrollStore(selectGlobalProgress);
|
|
743
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
744
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
745
|
+
react.useEffect(() => {
|
|
746
|
+
if (!onProgress) return;
|
|
747
|
+
let lastCall = 0;
|
|
748
|
+
const now = Date.now();
|
|
749
|
+
if (now - lastCall >= throttle2) {
|
|
750
|
+
lastCall = now;
|
|
751
|
+
onProgress(progress);
|
|
752
|
+
}
|
|
753
|
+
}, [progress, onProgress, throttle2]);
|
|
754
|
+
return {
|
|
755
|
+
progress,
|
|
756
|
+
activeIndex,
|
|
757
|
+
totalViews,
|
|
758
|
+
percentage: Math.round(progress * 100)
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
var DEFAULT_CONFIG = {
|
|
762
|
+
enabled: false,
|
|
763
|
+
interval: 5e3,
|
|
764
|
+
pauseOnInteraction: true,
|
|
765
|
+
resumeDelay: 3e3,
|
|
766
|
+
direction: "forward",
|
|
767
|
+
stopAtEnd: false
|
|
768
|
+
};
|
|
769
|
+
function useAutoScroll(config) {
|
|
770
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
771
|
+
const {
|
|
772
|
+
enabled,
|
|
773
|
+
interval,
|
|
774
|
+
pauseOnInteraction,
|
|
775
|
+
resumeDelay,
|
|
776
|
+
direction,
|
|
777
|
+
stopAtEnd
|
|
778
|
+
} = mergedConfig;
|
|
779
|
+
const intervalRef = react.useRef(null);
|
|
780
|
+
const resumeTimeoutRef = react.useRef(null);
|
|
781
|
+
const isAutoScrolling = useScrollStore((s) => s.isAutoScrolling);
|
|
782
|
+
const isAutoScrollPaused = useScrollStore((s) => s.isAutoScrollPaused);
|
|
783
|
+
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
784
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
785
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
786
|
+
const infiniteScrollEnabled = useScrollStore((s) => s.infiniteScrollEnabled);
|
|
787
|
+
const isDragging = useScrollStore((s) => s.isDragging);
|
|
788
|
+
const setAutoScrolling = useScrollStore((s) => s.setAutoScrolling);
|
|
789
|
+
const setAutoScrollPaused = useScrollStore((s) => s.setAutoScrollPaused);
|
|
790
|
+
const goToNext = useScrollStore((s) => s.goToNext);
|
|
791
|
+
const goToPrevious = useScrollStore((s) => s.goToPrevious);
|
|
792
|
+
const pause = react.useCallback(() => {
|
|
793
|
+
setAutoScrollPaused(true);
|
|
794
|
+
if (resumeTimeoutRef.current) {
|
|
795
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
796
|
+
resumeTimeoutRef.current = null;
|
|
797
|
+
}
|
|
798
|
+
}, [setAutoScrollPaused]);
|
|
799
|
+
const resume = react.useCallback(() => {
|
|
800
|
+
setAutoScrollPaused(false);
|
|
801
|
+
}, [setAutoScrollPaused]);
|
|
802
|
+
const toggle = react.useCallback(() => {
|
|
803
|
+
if (isAutoScrollPaused) {
|
|
804
|
+
resume();
|
|
805
|
+
} else {
|
|
806
|
+
pause();
|
|
807
|
+
}
|
|
808
|
+
}, [isAutoScrollPaused, pause, resume]);
|
|
809
|
+
const reset = react.useCallback(() => {
|
|
810
|
+
if (intervalRef.current) {
|
|
811
|
+
clearInterval(intervalRef.current);
|
|
812
|
+
intervalRef.current = null;
|
|
813
|
+
}
|
|
814
|
+
}, []);
|
|
815
|
+
react.useEffect(() => {
|
|
816
|
+
if (!enabled || !pauseOnInteraction) return;
|
|
817
|
+
if (isDragging || isTransitioning) {
|
|
818
|
+
pause();
|
|
819
|
+
if (resumeTimeoutRef.current) {
|
|
820
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
821
|
+
}
|
|
822
|
+
resumeTimeoutRef.current = setTimeout(() => {
|
|
823
|
+
if (enabled) {
|
|
824
|
+
resume();
|
|
825
|
+
}
|
|
826
|
+
}, resumeDelay);
|
|
827
|
+
}
|
|
828
|
+
return () => {
|
|
829
|
+
if (resumeTimeoutRef.current) {
|
|
830
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}, [isDragging, isTransitioning, enabled, pauseOnInteraction, resumeDelay, pause, resume]);
|
|
834
|
+
react.useEffect(() => {
|
|
835
|
+
setAutoScrolling(enabled);
|
|
836
|
+
setAutoScrollPaused(false);
|
|
837
|
+
return () => {
|
|
838
|
+
setAutoScrolling(false);
|
|
839
|
+
};
|
|
840
|
+
}, [enabled, setAutoScrolling, setAutoScrollPaused]);
|
|
841
|
+
react.useEffect(() => {
|
|
842
|
+
if (!enabled || isAutoScrollPaused || isTransitioning) {
|
|
843
|
+
reset();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (stopAtEnd && !infiniteScrollEnabled) {
|
|
847
|
+
if (direction === "forward" && activeIndex >= totalViews - 1) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (direction === "backward" && activeIndex <= 0) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
intervalRef.current = setInterval(() => {
|
|
855
|
+
if (direction === "forward") {
|
|
856
|
+
goToNext();
|
|
857
|
+
} else {
|
|
858
|
+
goToPrevious();
|
|
859
|
+
}
|
|
860
|
+
}, interval);
|
|
861
|
+
return () => {
|
|
862
|
+
reset();
|
|
863
|
+
};
|
|
864
|
+
}, [
|
|
865
|
+
enabled,
|
|
866
|
+
isAutoScrollPaused,
|
|
867
|
+
isTransitioning,
|
|
868
|
+
interval,
|
|
869
|
+
direction,
|
|
870
|
+
stopAtEnd,
|
|
871
|
+
activeIndex,
|
|
872
|
+
totalViews,
|
|
873
|
+
infiniteScrollEnabled,
|
|
874
|
+
goToNext,
|
|
875
|
+
goToPrevious,
|
|
876
|
+
reset
|
|
877
|
+
]);
|
|
878
|
+
return {
|
|
879
|
+
isPlaying: isAutoScrolling && !isAutoScrollPaused,
|
|
880
|
+
isPaused: isAutoScrollPaused,
|
|
881
|
+
pause,
|
|
882
|
+
resume,
|
|
883
|
+
toggle,
|
|
884
|
+
reset
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
var DEFAULT_CONFIG2 = {
|
|
888
|
+
enabled: false,
|
|
889
|
+
loopDirection: "both"
|
|
890
|
+
};
|
|
891
|
+
function useInfiniteScroll(config = false) {
|
|
892
|
+
const normalizedConfig = typeof config === "boolean" ? { ...DEFAULT_CONFIG2, enabled: config } : { ...DEFAULT_CONFIG2, ...config };
|
|
893
|
+
const { enabled, loopDirection } = normalizedConfig;
|
|
894
|
+
const infiniteScrollEnabled = useScrollStore((s) => s.infiniteScrollEnabled);
|
|
895
|
+
const setInfiniteScrollEnabled = useScrollStore((s) => s.setInfiniteScrollEnabled);
|
|
896
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
897
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
898
|
+
react.useEffect(() => {
|
|
899
|
+
setInfiniteScrollEnabled(enabled);
|
|
900
|
+
return () => {
|
|
901
|
+
};
|
|
902
|
+
}, [enabled, setInfiniteScrollEnabled]);
|
|
903
|
+
const enable = () => setInfiniteScrollEnabled(true);
|
|
904
|
+
const disable = () => setInfiniteScrollEnabled(false);
|
|
905
|
+
const toggle = () => setInfiniteScrollEnabled(!infiniteScrollEnabled);
|
|
906
|
+
const canLoopForward = infiniteScrollEnabled && (loopDirection === "forward" || loopDirection === "both") && activeIndex === totalViews - 1;
|
|
907
|
+
const canLoopBackward = infiniteScrollEnabled && (loopDirection === "backward" || loopDirection === "both") && activeIndex === 0;
|
|
908
|
+
return {
|
|
909
|
+
isEnabled: infiniteScrollEnabled,
|
|
910
|
+
enable,
|
|
911
|
+
disable,
|
|
912
|
+
toggle,
|
|
913
|
+
canLoopForward,
|
|
914
|
+
canLoopBackward
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
var DEFAULT_CONFIG3 = {
|
|
918
|
+
ahead: 1,
|
|
919
|
+
behind: 1,
|
|
920
|
+
delay: 100
|
|
921
|
+
};
|
|
922
|
+
function usePreload(config = {}) {
|
|
923
|
+
const mergedConfig = { ...DEFAULT_CONFIG3, ...config };
|
|
924
|
+
const { ahead, behind, delay } = mergedConfig;
|
|
925
|
+
const views = useScrollStore((s) => s.views);
|
|
926
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
927
|
+
const totalViews = useScrollStore((s) => s.totalViews);
|
|
928
|
+
const infiniteScrollEnabled = useScrollStore((s) => s.infiniteScrollEnabled);
|
|
929
|
+
const setViewPreloaded = useScrollStore((s) => s.setViewPreloaded);
|
|
930
|
+
const preloadedViewIds = react.useMemo(() => {
|
|
931
|
+
const ids = [];
|
|
932
|
+
for (let i = -behind; i <= ahead; i++) {
|
|
933
|
+
let targetIndex = activeIndex + i;
|
|
934
|
+
if (infiniteScrollEnabled) {
|
|
935
|
+
if (targetIndex < 0) targetIndex = totalViews + targetIndex;
|
|
936
|
+
if (targetIndex >= totalViews) targetIndex = targetIndex - totalViews;
|
|
937
|
+
}
|
|
938
|
+
if (targetIndex >= 0 && targetIndex < totalViews) {
|
|
939
|
+
const view = views[targetIndex];
|
|
940
|
+
if (view) {
|
|
941
|
+
ids.push(view.id);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return ids;
|
|
946
|
+
}, [activeIndex, ahead, behind, views, totalViews, infiniteScrollEnabled]);
|
|
947
|
+
react.useEffect(() => {
|
|
948
|
+
const timer = setTimeout(() => {
|
|
949
|
+
views.forEach((view) => {
|
|
950
|
+
const shouldBePreloaded = preloadedViewIds.includes(view.id);
|
|
951
|
+
if (view.isPreloaded !== shouldBePreloaded) {
|
|
952
|
+
setViewPreloaded(view.id, shouldBePreloaded);
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
}, delay);
|
|
956
|
+
return () => clearTimeout(timer);
|
|
957
|
+
}, [preloadedViewIds, views, setViewPreloaded, delay]);
|
|
958
|
+
const shouldPreload = (viewId) => {
|
|
959
|
+
return preloadedViewIds.includes(viewId);
|
|
960
|
+
};
|
|
961
|
+
const isPreloaded = (viewId) => {
|
|
962
|
+
const view = views.find((v) => v.id === viewId);
|
|
963
|
+
return view?.isPreloaded ?? false;
|
|
964
|
+
};
|
|
965
|
+
return {
|
|
966
|
+
shouldPreload,
|
|
967
|
+
preloadedViewIds,
|
|
968
|
+
isPreloaded
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
var DEFAULT_GESTURE_CONFIG = {
|
|
972
|
+
swipeThreshold: 50,
|
|
973
|
+
swipeVelocity: 0.5,
|
|
974
|
+
dragResistance: 0.3,
|
|
975
|
+
enableWheel: true,
|
|
976
|
+
enableTouch: true,
|
|
977
|
+
enableKeyboard: true
|
|
978
|
+
};
|
|
979
|
+
var GestureConfigContext = react.createContext(DEFAULT_GESTURE_CONFIG);
|
|
980
|
+
function useGestureConfig() {
|
|
981
|
+
return react.useContext(GestureConfigContext);
|
|
982
|
+
}
|
|
983
|
+
function mergeGestureConfig(config) {
|
|
984
|
+
return react.useMemo(() => ({
|
|
985
|
+
...DEFAULT_GESTURE_CONFIG,
|
|
986
|
+
...config
|
|
987
|
+
}), [config]);
|
|
988
|
+
}
|
|
736
989
|
|
|
737
990
|
// utils/index.ts
|
|
738
991
|
function throttle(fn, limit) {
|
|
@@ -761,9 +1014,6 @@ function prefersReducedMotion() {
|
|
|
761
1014
|
if (typeof window === "undefined") return false;
|
|
762
1015
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
763
1016
|
}
|
|
764
|
-
|
|
765
|
-
// components/ScrollContainer.tsx
|
|
766
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
767
1017
|
function ScrollContainer({
|
|
768
1018
|
children,
|
|
769
1019
|
className = "",
|
|
@@ -781,12 +1031,20 @@ function ScrollContainer({
|
|
|
781
1031
|
// Touch Physics
|
|
782
1032
|
enableDragPhysics = false,
|
|
783
1033
|
// Layout
|
|
784
|
-
orientation = "vertical"
|
|
1034
|
+
orientation = "vertical",
|
|
1035
|
+
// NEW: v1.1.0 Features
|
|
1036
|
+
skipInitialAnimation = false,
|
|
1037
|
+
onProgress,
|
|
1038
|
+
gestureConfig,
|
|
1039
|
+
autoScroll,
|
|
1040
|
+
infiniteScroll = false,
|
|
1041
|
+
preload = true
|
|
785
1042
|
}) {
|
|
786
|
-
const containerRef =
|
|
1043
|
+
const containerRef = react.useRef(null);
|
|
1044
|
+
const isFirstRender = react.useRef(true);
|
|
787
1045
|
const isBrowser = typeof window !== "undefined";
|
|
788
|
-
const [reducedMotion, setReducedMotion] =
|
|
789
|
-
|
|
1046
|
+
const [reducedMotion, setReducedMotion] = react.useState(false);
|
|
1047
|
+
react.useEffect(() => {
|
|
790
1048
|
if (!isBrowser || !respectReducedMotion) return;
|
|
791
1049
|
setReducedMotion(prefersReducedMotion());
|
|
792
1050
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
@@ -797,9 +1055,9 @@ function ScrollContainer({
|
|
|
797
1055
|
const effectiveDuration = reducedMotion ? 0 : transitionDuration;
|
|
798
1056
|
const { initialize, endTransition } = useScrollStore();
|
|
799
1057
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const prevIndexRef =
|
|
1058
|
+
useScrollStore((s) => s.totalViews);
|
|
1059
|
+
useScrollStore((s) => s.isInitialized);
|
|
1060
|
+
const prevIndexRef = react.useRef(activeIndex);
|
|
803
1061
|
useWheelHandler();
|
|
804
1062
|
useTouchHandler({ enabled: !enableDragPhysics });
|
|
805
1063
|
useKeyboardHandler();
|
|
@@ -812,14 +1070,39 @@ function ScrollContainer({
|
|
|
812
1070
|
hashPrefix
|
|
813
1071
|
});
|
|
814
1072
|
useFocusManagement({ enabled: enableFocusManagement });
|
|
815
|
-
(
|
|
1073
|
+
useGlobalProgress({ onProgress });
|
|
1074
|
+
useInfiniteScroll(infiniteScroll);
|
|
1075
|
+
const autoScrollState = useAutoScroll(
|
|
1076
|
+
autoScroll ?? { enabled: false, interval: 5e3 }
|
|
1077
|
+
);
|
|
1078
|
+
const preloadConfig = typeof preload === "boolean" ? preload ? { ahead: 1, behind: 1 } : void 0 : preload;
|
|
1079
|
+
usePreload(preloadConfig ?? {});
|
|
1080
|
+
const mergedGestureConfig = mergeGestureConfig(gestureConfig);
|
|
1081
|
+
react.useEffect(() => {
|
|
816
1082
|
const timer = setTimeout(() => {
|
|
817
1083
|
initialize();
|
|
818
1084
|
onInitialized?.();
|
|
1085
|
+
isFirstRender.current = false;
|
|
819
1086
|
}, 50);
|
|
820
1087
|
return () => clearTimeout(timer);
|
|
821
1088
|
}, [initialize, onInitialized]);
|
|
822
|
-
|
|
1089
|
+
react.useEffect(() => {
|
|
1090
|
+
if (!isBrowser) return;
|
|
1091
|
+
const html = document.documentElement;
|
|
1092
|
+
const body = document.body;
|
|
1093
|
+
const originalHtmlOverscroll = html.style.overscrollBehavior;
|
|
1094
|
+
const originalBodyOverscroll = body.style.overscrollBehavior;
|
|
1095
|
+
const originalTouchAction = body.style.touchAction;
|
|
1096
|
+
html.style.overscrollBehavior = "none";
|
|
1097
|
+
body.style.overscrollBehavior = "none";
|
|
1098
|
+
body.style.touchAction = "pan-x pan-y";
|
|
1099
|
+
return () => {
|
|
1100
|
+
html.style.overscrollBehavior = originalHtmlOverscroll;
|
|
1101
|
+
body.style.overscrollBehavior = originalBodyOverscroll;
|
|
1102
|
+
body.style.touchAction = originalTouchAction;
|
|
1103
|
+
};
|
|
1104
|
+
}, [isBrowser]);
|
|
1105
|
+
react.useEffect(() => {
|
|
823
1106
|
if (prevIndexRef.current !== activeIndex) {
|
|
824
1107
|
onViewChange?.(prevIndexRef.current, activeIndex);
|
|
825
1108
|
const timer = setTimeout(() => {
|
|
@@ -829,37 +1112,34 @@ function ScrollContainer({
|
|
|
829
1112
|
return () => clearTimeout(timer);
|
|
830
1113
|
}
|
|
831
1114
|
}, [activeIndex, effectiveDuration, onViewChange, endTransition]);
|
|
832
|
-
const wrapperStyle =
|
|
1115
|
+
const wrapperStyle = react.useMemo(() => {
|
|
833
1116
|
const baseOffset = activeIndex * 100;
|
|
834
1117
|
const dragOffset = dragState.isDragging ? dragState.dragOffset * 100 : 0;
|
|
835
1118
|
const transformAxis = orientation === "horizontal" ? "X" : "Y";
|
|
836
1119
|
const sizeUnit = orientation === "horizontal" ? "vw" : "vh";
|
|
1120
|
+
const shouldAnimate = !skipInitialAnimation || !isFirstRender.current;
|
|
1121
|
+
const transitionValue = dragState.isDragging ? "none" : shouldAnimate && effectiveDuration > 0 ? `transform ${effectiveDuration}ms ${transitionEasing}` : "none";
|
|
837
1122
|
return {
|
|
838
1123
|
transform: `translate${transformAxis}(-${baseOffset + dragOffset}${sizeUnit})`,
|
|
839
|
-
transition:
|
|
1124
|
+
transition: transitionValue,
|
|
840
1125
|
height: "100%",
|
|
841
1126
|
width: "100%",
|
|
842
1127
|
display: orientation === "horizontal" ? "flex" : "block",
|
|
843
1128
|
flexDirection: orientation === "horizontal" ? "row" : void 0
|
|
844
1129
|
};
|
|
845
|
-
}, [activeIndex, effectiveDuration, transitionEasing, dragState, orientation]);
|
|
846
|
-
return /* @__PURE__ */ (
|
|
1130
|
+
}, [activeIndex, effectiveDuration, transitionEasing, dragState, orientation, skipInitialAnimation]);
|
|
1131
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GestureConfigContext.Provider, { value: mergedGestureConfig, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
847
1132
|
"div",
|
|
848
1133
|
{
|
|
849
1134
|
ref: containerRef,
|
|
850
1135
|
className: `scroll-container fixed inset-0 overflow-hidden w-screen h-screen ${className}`,
|
|
851
1136
|
role: "main",
|
|
852
1137
|
"aria-label": "Scroll container",
|
|
853
|
-
|
|
1138
|
+
"data-auto-scrolling": autoScrollState.isPlaying,
|
|
1139
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "scroll-wrapper", style: wrapperStyle, children })
|
|
854
1140
|
}
|
|
855
|
-
);
|
|
1141
|
+
) });
|
|
856
1142
|
}
|
|
857
|
-
|
|
858
|
-
// components/FullView.tsx
|
|
859
|
-
var import_react10 = require("react");
|
|
860
|
-
|
|
861
|
-
// hooks/useViewRegistration.ts
|
|
862
|
-
var import_react9 = require("react");
|
|
863
1143
|
function useViewRegistration({
|
|
864
1144
|
config,
|
|
865
1145
|
onActivate,
|
|
@@ -873,7 +1153,7 @@ function useViewRegistration({
|
|
|
873
1153
|
const unregisterView = useScrollStore((s) => s.unregisterView);
|
|
874
1154
|
const activeId = useScrollStore((s) => s.activeId);
|
|
875
1155
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
876
|
-
const callbacksRef =
|
|
1156
|
+
const callbacksRef = react.useRef({
|
|
877
1157
|
onActivate,
|
|
878
1158
|
onDeactivate,
|
|
879
1159
|
onEnterStart,
|
|
@@ -889,20 +1169,20 @@ function useViewRegistration({
|
|
|
889
1169
|
onExitStart,
|
|
890
1170
|
onExitEnd
|
|
891
1171
|
};
|
|
892
|
-
const wasActiveRef =
|
|
893
|
-
const wasTransitioningRef =
|
|
894
|
-
const configRef =
|
|
1172
|
+
const wasActiveRef = react.useRef(false);
|
|
1173
|
+
const wasTransitioningRef = react.useRef(false);
|
|
1174
|
+
const configRef = react.useRef(config);
|
|
895
1175
|
if (JSON.stringify(config) !== JSON.stringify(configRef.current)) {
|
|
896
1176
|
configRef.current = config;
|
|
897
1177
|
}
|
|
898
|
-
|
|
1178
|
+
react.useEffect(() => {
|
|
899
1179
|
const currentConfig = configRef.current;
|
|
900
1180
|
registerView(currentConfig);
|
|
901
1181
|
return () => unregisterView(currentConfig.id);
|
|
902
1182
|
}, [registerView, unregisterView, configRef.current]);
|
|
903
1183
|
const viewState = useScrollStore((s) => s.views.find((v) => v.id === config.id));
|
|
904
1184
|
const isActive = activeId === config.id;
|
|
905
|
-
|
|
1185
|
+
react.useEffect(() => {
|
|
906
1186
|
if (isActive && !wasActiveRef.current) {
|
|
907
1187
|
callbacksRef.current.onActivate?.();
|
|
908
1188
|
} else if (!isActive && wasActiveRef.current) {
|
|
@@ -910,7 +1190,7 @@ function useViewRegistration({
|
|
|
910
1190
|
}
|
|
911
1191
|
wasActiveRef.current = isActive;
|
|
912
1192
|
}, [isActive]);
|
|
913
|
-
|
|
1193
|
+
react.useEffect(() => {
|
|
914
1194
|
const callbacks = callbacksRef.current;
|
|
915
1195
|
const wasActive = wasActiveRef.current;
|
|
916
1196
|
const wasTransitioning = wasTransitioningRef.current;
|
|
@@ -938,9 +1218,6 @@ function useViewRegistration({
|
|
|
938
1218
|
navigation: viewState?.navigation ?? "unlocked"
|
|
939
1219
|
};
|
|
940
1220
|
}
|
|
941
|
-
|
|
942
|
-
// components/FullView.tsx
|
|
943
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
944
1221
|
function FullView({
|
|
945
1222
|
id,
|
|
946
1223
|
children,
|
|
@@ -953,7 +1230,7 @@ function FullView({
|
|
|
953
1230
|
onExitStart,
|
|
954
1231
|
onExitEnd
|
|
955
1232
|
}) {
|
|
956
|
-
const config =
|
|
1233
|
+
const config = react.useMemo(
|
|
957
1234
|
() => ({
|
|
958
1235
|
id,
|
|
959
1236
|
type: "full",
|
|
@@ -970,7 +1247,7 @@ function FullView({
|
|
|
970
1247
|
onExitStart,
|
|
971
1248
|
onExitEnd
|
|
972
1249
|
});
|
|
973
|
-
return /* @__PURE__ */
|
|
1250
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
974
1251
|
"section",
|
|
975
1252
|
{
|
|
976
1253
|
id,
|
|
@@ -990,9 +1267,6 @@ function FullView({
|
|
|
990
1267
|
}
|
|
991
1268
|
);
|
|
992
1269
|
}
|
|
993
|
-
|
|
994
|
-
// hooks/useMetricsReporter.ts
|
|
995
|
-
var import_react11 = require("react");
|
|
996
1270
|
var SCROLL_THROTTLE_MS = 66;
|
|
997
1271
|
function useMetricsReporter({
|
|
998
1272
|
id,
|
|
@@ -1001,9 +1275,9 @@ function useMetricsReporter({
|
|
|
1001
1275
|
onScrollProgress,
|
|
1002
1276
|
throttleMs = SCROLL_THROTTLE_MS
|
|
1003
1277
|
}) {
|
|
1004
|
-
const scrollRef =
|
|
1278
|
+
const scrollRef = react.useRef(null);
|
|
1005
1279
|
const updateMetrics = useScrollStore((s) => s.updateViewMetrics);
|
|
1006
|
-
const measureAndReport =
|
|
1280
|
+
const measureAndReport = react.useCallback(() => {
|
|
1007
1281
|
const el = scrollRef.current;
|
|
1008
1282
|
if (!el) return;
|
|
1009
1283
|
const metrics = {
|
|
@@ -1018,11 +1292,11 @@ function useMetricsReporter({
|
|
|
1018
1292
|
onScrollProgress(progress);
|
|
1019
1293
|
}
|
|
1020
1294
|
}, [id, scrollDirection, updateMetrics, onScrollProgress]);
|
|
1021
|
-
const throttledMeasure =
|
|
1295
|
+
const throttledMeasure = react.useMemo(
|
|
1022
1296
|
() => throttle(measureAndReport, throttleMs),
|
|
1023
1297
|
[measureAndReport, throttleMs]
|
|
1024
1298
|
);
|
|
1025
|
-
|
|
1299
|
+
react.useEffect(() => {
|
|
1026
1300
|
const el = scrollRef.current;
|
|
1027
1301
|
if (!el) return;
|
|
1028
1302
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -1037,16 +1311,13 @@ function useMetricsReporter({
|
|
|
1037
1311
|
el.removeEventListener("scroll", throttledMeasure);
|
|
1038
1312
|
};
|
|
1039
1313
|
}, [measureAndReport, throttledMeasure]);
|
|
1040
|
-
|
|
1314
|
+
react.useEffect(() => {
|
|
1041
1315
|
if (isActive) {
|
|
1042
1316
|
measureAndReport();
|
|
1043
1317
|
}
|
|
1044
1318
|
}, [isActive, measureAndReport]);
|
|
1045
1319
|
return { scrollRef, measureAndReport };
|
|
1046
1320
|
}
|
|
1047
|
-
|
|
1048
|
-
// components/ScrollLockedView.tsx
|
|
1049
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1050
1321
|
function ScrollLockedView({
|
|
1051
1322
|
id,
|
|
1052
1323
|
children,
|
|
@@ -1082,14 +1353,14 @@ function ScrollLockedView({
|
|
|
1082
1353
|
onScrollProgress
|
|
1083
1354
|
});
|
|
1084
1355
|
const scrollClasses = scrollDirection === "vertical" ? "overflow-y-auto overflow-x-hidden" : "overflow-x-auto overflow-y-hidden";
|
|
1085
|
-
return /* @__PURE__ */
|
|
1356
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1086
1357
|
"section",
|
|
1087
1358
|
{
|
|
1088
1359
|
id,
|
|
1089
1360
|
className: `relative w-full h-screen ${className}`,
|
|
1090
1361
|
"data-view-type": "scroll-locked",
|
|
1091
1362
|
"data-active": isActive,
|
|
1092
|
-
children: /* @__PURE__ */
|
|
1363
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1093
1364
|
"div",
|
|
1094
1365
|
{
|
|
1095
1366
|
ref: scrollRef,
|
|
@@ -1101,10 +1372,6 @@ function ScrollLockedView({
|
|
|
1101
1372
|
}
|
|
1102
1373
|
);
|
|
1103
1374
|
}
|
|
1104
|
-
|
|
1105
|
-
// components/ControlledView.tsx
|
|
1106
|
-
var import_react12 = require("react");
|
|
1107
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1108
1375
|
function ControlledView({
|
|
1109
1376
|
id,
|
|
1110
1377
|
children,
|
|
@@ -1121,7 +1388,7 @@ function ControlledView({
|
|
|
1121
1388
|
onExitEnd
|
|
1122
1389
|
}) {
|
|
1123
1390
|
const setExplicitLock = useScrollStore((s) => s.setViewExplicitLock);
|
|
1124
|
-
const config =
|
|
1391
|
+
const config = react.useMemo(
|
|
1125
1392
|
() => ({
|
|
1126
1393
|
id,
|
|
1127
1394
|
type: "controlled",
|
|
@@ -1145,11 +1412,11 @@ function ControlledView({
|
|
|
1145
1412
|
isActive,
|
|
1146
1413
|
scrollDirection: allowInternalScroll ? scrollDirection : "none"
|
|
1147
1414
|
});
|
|
1148
|
-
|
|
1415
|
+
react.useEffect(() => {
|
|
1149
1416
|
const lockState = canProceed ? "unlocked" : "locked";
|
|
1150
1417
|
setExplicitLock(id, lockState);
|
|
1151
1418
|
}, [id, canProceed, setExplicitLock]);
|
|
1152
|
-
const overflowClasses =
|
|
1419
|
+
const overflowClasses = react.useMemo(() => {
|
|
1153
1420
|
if (!allowInternalScroll) return "overflow-hidden";
|
|
1154
1421
|
switch (scrollDirection) {
|
|
1155
1422
|
case "vertical":
|
|
@@ -1160,14 +1427,14 @@ function ControlledView({
|
|
|
1160
1427
|
return "overflow-auto";
|
|
1161
1428
|
}
|
|
1162
1429
|
}, [allowInternalScroll, scrollDirection]);
|
|
1163
|
-
return /* @__PURE__ */
|
|
1430
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1164
1431
|
"section",
|
|
1165
1432
|
{
|
|
1166
1433
|
id,
|
|
1167
1434
|
className: `relative w-full h-screen ${isActive ? "z-10" : "z-0"} ${className}`,
|
|
1168
1435
|
"data-view-type": "controlled",
|
|
1169
1436
|
"data-active": isActive,
|
|
1170
|
-
children: /* @__PURE__ */
|
|
1437
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1171
1438
|
"div",
|
|
1172
1439
|
{
|
|
1173
1440
|
ref: scrollRef,
|
|
@@ -1183,7 +1450,7 @@ function useViewControl(viewId) {
|
|
|
1183
1450
|
const goToNext = useScrollStore((s) => s.goToNext);
|
|
1184
1451
|
const goToPrevious = useScrollStore((s) => s.goToPrevious);
|
|
1185
1452
|
const goToView = useScrollStore((s) => s.goToView);
|
|
1186
|
-
return
|
|
1453
|
+
return react.useMemo(
|
|
1187
1454
|
() => ({
|
|
1188
1455
|
unlock: () => setExplicitLock(viewId, "unlocked"),
|
|
1189
1456
|
lock: () => setExplicitLock(viewId, "locked"),
|
|
@@ -1195,9 +1462,6 @@ function useViewControl(viewId) {
|
|
|
1195
1462
|
[viewId, setExplicitLock, goToNext, goToPrevious, goToView]
|
|
1196
1463
|
);
|
|
1197
1464
|
}
|
|
1198
|
-
|
|
1199
|
-
// components/ScrollDebugOverlay.tsx
|
|
1200
|
-
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1201
1465
|
function ScrollDebugOverlay({
|
|
1202
1466
|
position = "bottom-left",
|
|
1203
1467
|
visible = true
|
|
@@ -1216,36 +1480,36 @@ function ScrollDebugOverlay({
|
|
|
1216
1480
|
"bottom-left": "bottom-4 left-4",
|
|
1217
1481
|
"bottom-right": "bottom-4 right-4"
|
|
1218
1482
|
};
|
|
1219
|
-
return /* @__PURE__ */
|
|
1483
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1220
1484
|
"div",
|
|
1221
1485
|
{
|
|
1222
1486
|
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
1487
|
style: { pointerEvents: "none" },
|
|
1224
1488
|
children: [
|
|
1225
|
-
/* @__PURE__ */
|
|
1226
|
-
/* @__PURE__ */
|
|
1489
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-green-300 font-bold mb-2 flex items-center gap-2", children: [
|
|
1490
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-2 h-2 rounded-full bg-green-500 animate-pulse" }),
|
|
1227
1491
|
"ScrollSystem Debug"
|
|
1228
1492
|
] }),
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
/* @__PURE__ */
|
|
1493
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 mb-3", children: [
|
|
1494
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "initialized", value: isInitialized }),
|
|
1495
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "activeIndex", value: `${activeIndex} / ${totalViews - 1}` }),
|
|
1496
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "transitioning", value: isTransitioning }),
|
|
1497
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "globalLocked", value: isGlobalLocked })
|
|
1234
1498
|
] }),
|
|
1235
|
-
activeView && /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1238
|
-
/* @__PURE__ */
|
|
1239
|
-
/* @__PURE__ */
|
|
1240
|
-
/* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
1242
|
-
activeView.explicitLock && /* @__PURE__ */
|
|
1499
|
+
activeView && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
|
|
1500
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-green-300/70 mb-1", children: "Active View" }),
|
|
1501
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "id", value: activeView.id }),
|
|
1502
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "type", value: activeView.type }),
|
|
1503
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "capability", value: activeView.capability }),
|
|
1504
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "navigation", value: activeView.navigation }),
|
|
1505
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "progress", value: `${(activeView.progress * 100).toFixed(0)}%` }),
|
|
1506
|
+
activeView.explicitLock && /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "explicitLock", value: activeView.explicitLock, highlight: true })
|
|
1243
1507
|
] }),
|
|
1244
|
-
activeView?.metrics && /* @__PURE__ */
|
|
1245
|
-
/* @__PURE__ */
|
|
1246
|
-
/* @__PURE__ */
|
|
1247
|
-
/* @__PURE__ */
|
|
1248
|
-
/* @__PURE__ */
|
|
1508
|
+
activeView?.metrics && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
|
|
1509
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-green-300/70 mb-1", children: "Metrics" }),
|
|
1510
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "scrollHeight", value: activeView.metrics.scrollHeight }),
|
|
1511
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "clientHeight", value: activeView.metrics.clientHeight }),
|
|
1512
|
+
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "scrollTop", value: Math.round(activeView.metrics.scrollTop) })
|
|
1249
1513
|
] })
|
|
1250
1514
|
]
|
|
1251
1515
|
}
|
|
@@ -1258,28 +1522,24 @@ function Row({
|
|
|
1258
1522
|
}) {
|
|
1259
1523
|
const displayValue = typeof value === "boolean" ? value ? "\u2713" : "\u2717" : String(value);
|
|
1260
1524
|
const valueColor = typeof value === "boolean" ? value ? "text-green-400" : "text-red-400" : highlight ? "text-yellow-400" : "text-white";
|
|
1261
|
-
return /* @__PURE__ */
|
|
1262
|
-
/* @__PURE__ */
|
|
1525
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between gap-4", children: [
|
|
1526
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-green-500/70", children: [
|
|
1263
1527
|
label,
|
|
1264
1528
|
":"
|
|
1265
1529
|
] }),
|
|
1266
|
-
/* @__PURE__ */
|
|
1530
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: valueColor, children: displayValue })
|
|
1267
1531
|
] });
|
|
1268
1532
|
}
|
|
1269
|
-
|
|
1270
|
-
// components/AriaLiveRegion.tsx
|
|
1271
|
-
var import_react13 = require("react");
|
|
1272
|
-
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1273
1533
|
function AriaLiveRegion({
|
|
1274
1534
|
template = "Navigated to section {viewIndex} of {totalViews}",
|
|
1275
1535
|
politeness = "polite"
|
|
1276
1536
|
}) {
|
|
1277
|
-
const [announcement, setAnnouncement] =
|
|
1537
|
+
const [announcement, setAnnouncement] = react.useState("");
|
|
1278
1538
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1279
1539
|
const activeId = useScrollStore((s) => s.activeId);
|
|
1280
1540
|
const totalViews = useScrollStore((s) => s.totalViews);
|
|
1281
1541
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
1282
|
-
|
|
1542
|
+
react.useEffect(() => {
|
|
1283
1543
|
if (!isTransitioning && activeId) {
|
|
1284
1544
|
const message = template.replace("{viewIndex}", String(activeIndex + 1)).replace("{viewId}", activeId).replace("{totalViews}", String(totalViews));
|
|
1285
1545
|
const timer = setTimeout(() => {
|
|
@@ -1288,7 +1548,7 @@ function AriaLiveRegion({
|
|
|
1288
1548
|
return () => clearTimeout(timer);
|
|
1289
1549
|
}
|
|
1290
1550
|
}, [activeIndex, activeId, totalViews, isTransitioning, template]);
|
|
1291
|
-
return /* @__PURE__ */
|
|
1551
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1292
1552
|
"div",
|
|
1293
1553
|
{
|
|
1294
1554
|
role: "status",
|
|
@@ -1310,10 +1570,6 @@ function AriaLiveRegion({
|
|
|
1310
1570
|
}
|
|
1311
1571
|
);
|
|
1312
1572
|
}
|
|
1313
|
-
|
|
1314
|
-
// components/LazyView.tsx
|
|
1315
|
-
var import_react14 = require("react");
|
|
1316
|
-
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1317
1573
|
function LazyView({
|
|
1318
1574
|
viewId,
|
|
1319
1575
|
buffer = 1,
|
|
@@ -1322,7 +1578,7 @@ function LazyView({
|
|
|
1322
1578
|
}) {
|
|
1323
1579
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1324
1580
|
const views = useScrollStore((s) => s.views);
|
|
1325
|
-
const shouldRender =
|
|
1581
|
+
const shouldRender = react.useMemo(() => {
|
|
1326
1582
|
const viewIndex = views.findIndex((v) => v.id === viewId);
|
|
1327
1583
|
if (viewIndex === -1) return false;
|
|
1328
1584
|
const minIndex = Math.max(0, activeIndex - buffer);
|
|
@@ -1330,20 +1586,149 @@ function LazyView({
|
|
|
1330
1586
|
return viewIndex >= minIndex && viewIndex <= maxIndex;
|
|
1331
1587
|
}, [viewId, views, activeIndex, buffer]);
|
|
1332
1588
|
if (!shouldRender) {
|
|
1333
|
-
return /* @__PURE__ */
|
|
1589
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: placeholder });
|
|
1334
1590
|
}
|
|
1335
|
-
return /* @__PURE__ */
|
|
1591
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1592
|
+
}
|
|
1593
|
+
function NestedScrollView({
|
|
1594
|
+
id,
|
|
1595
|
+
children,
|
|
1596
|
+
className = "",
|
|
1597
|
+
nestedDirection = "horizontal",
|
|
1598
|
+
enableSnap = true,
|
|
1599
|
+
onItemChange,
|
|
1600
|
+
onActivate,
|
|
1601
|
+
onDeactivate,
|
|
1602
|
+
onEnterStart,
|
|
1603
|
+
onEnterEnd,
|
|
1604
|
+
onExitStart,
|
|
1605
|
+
onExitEnd
|
|
1606
|
+
}) {
|
|
1607
|
+
const containerRef = react.useRef(null);
|
|
1608
|
+
const nestedContainerRef = react.useRef(null);
|
|
1609
|
+
const [activeNestedIndex, setActiveNestedIndex] = react.useState(0);
|
|
1610
|
+
const [isNestedScrolling, setIsNestedScrolling] = react.useState(false);
|
|
1611
|
+
useScrollStore((s) => s.activeIndex);
|
|
1612
|
+
const views = useScrollStore((s) => s.views);
|
|
1613
|
+
const setGlobalLock = useScrollStore((s) => s.setGlobalLock);
|
|
1614
|
+
const view = views.find((v) => v.id === id);
|
|
1615
|
+
const isActive = view?.isActive ?? false;
|
|
1616
|
+
useViewRegistration({
|
|
1617
|
+
config: {
|
|
1618
|
+
id,
|
|
1619
|
+
type: "nested",
|
|
1620
|
+
nestedConfig: {
|
|
1621
|
+
direction: nestedDirection,
|
|
1622
|
+
enableSnap,
|
|
1623
|
+
onItemChange
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
onActivate,
|
|
1627
|
+
onDeactivate,
|
|
1628
|
+
onEnterStart,
|
|
1629
|
+
onEnterEnd,
|
|
1630
|
+
onExitStart,
|
|
1631
|
+
onExitEnd
|
|
1632
|
+
});
|
|
1633
|
+
react.useEffect(() => {
|
|
1634
|
+
if (isActive) {
|
|
1635
|
+
onActivate?.();
|
|
1636
|
+
} else {
|
|
1637
|
+
onDeactivate?.();
|
|
1638
|
+
}
|
|
1639
|
+
}, [isActive, onActivate, onDeactivate]);
|
|
1640
|
+
const handleNestedScroll = react.useCallback((e) => {
|
|
1641
|
+
if (!nestedContainerRef.current) return;
|
|
1642
|
+
const container = nestedContainerRef.current;
|
|
1643
|
+
const scrollPos = nestedDirection === "horizontal" ? container.scrollLeft : container.scrollTop;
|
|
1644
|
+
const itemSize = nestedDirection === "horizontal" ? container.clientWidth : container.clientHeight;
|
|
1645
|
+
if (itemSize > 0) {
|
|
1646
|
+
const newIndex = Math.round(scrollPos / itemSize);
|
|
1647
|
+
if (newIndex !== activeNestedIndex) {
|
|
1648
|
+
setActiveNestedIndex(newIndex);
|
|
1649
|
+
onItemChange?.(newIndex);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}, [nestedDirection, activeNestedIndex, onItemChange]);
|
|
1653
|
+
const handleTouchStart = react.useCallback((e) => {
|
|
1654
|
+
setIsNestedScrolling(true);
|
|
1655
|
+
setGlobalLock(true);
|
|
1656
|
+
}, [setGlobalLock]);
|
|
1657
|
+
const handleTouchEnd = react.useCallback(() => {
|
|
1658
|
+
setTimeout(() => {
|
|
1659
|
+
setIsNestedScrolling(false);
|
|
1660
|
+
setGlobalLock(false);
|
|
1661
|
+
}, 100);
|
|
1662
|
+
}, [setGlobalLock]);
|
|
1663
|
+
react.useCallback((index) => {
|
|
1664
|
+
if (!nestedContainerRef.current) return;
|
|
1665
|
+
const container = nestedContainerRef.current;
|
|
1666
|
+
const itemSize = nestedDirection === "horizontal" ? container.clientWidth : container.clientHeight;
|
|
1667
|
+
const scrollPos = index * itemSize;
|
|
1668
|
+
container.scrollTo({
|
|
1669
|
+
[nestedDirection === "horizontal" ? "left" : "top"]: scrollPos,
|
|
1670
|
+
behavior: "smooth"
|
|
1671
|
+
});
|
|
1672
|
+
}, [nestedDirection]);
|
|
1673
|
+
const nestedScrollStyle = {
|
|
1674
|
+
display: "flex",
|
|
1675
|
+
flexDirection: nestedDirection === "horizontal" ? "row" : "column",
|
|
1676
|
+
overflow: nestedDirection === "horizontal" ? "auto hidden" : "hidden auto",
|
|
1677
|
+
scrollSnapType: enableSnap ? `${nestedDirection === "horizontal" ? "x" : "y"} mandatory` : "none",
|
|
1678
|
+
scrollBehavior: "smooth",
|
|
1679
|
+
WebkitOverflowScrolling: "touch",
|
|
1680
|
+
width: "100%",
|
|
1681
|
+
height: "100%"
|
|
1682
|
+
};
|
|
1683
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1684
|
+
"div",
|
|
1685
|
+
{
|
|
1686
|
+
ref: containerRef,
|
|
1687
|
+
className: `scroll-view nested-scroll-view h-screen w-screen flex-shrink-0 relative ${className}`,
|
|
1688
|
+
"data-view-id": id,
|
|
1689
|
+
"data-view-type": "nested",
|
|
1690
|
+
"data-nested-direction": nestedDirection,
|
|
1691
|
+
role: "region",
|
|
1692
|
+
"aria-label": `Nested scroll view ${id}`,
|
|
1693
|
+
tabIndex: 0,
|
|
1694
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1695
|
+
"div",
|
|
1696
|
+
{
|
|
1697
|
+
ref: nestedContainerRef,
|
|
1698
|
+
className: "nested-scroll-container",
|
|
1699
|
+
style: nestedScrollStyle,
|
|
1700
|
+
onScroll: handleNestedScroll,
|
|
1701
|
+
onTouchStart: handleTouchStart,
|
|
1702
|
+
onTouchEnd: handleTouchEnd,
|
|
1703
|
+
children
|
|
1704
|
+
}
|
|
1705
|
+
)
|
|
1706
|
+
}
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
function NestedScrollItem({
|
|
1710
|
+
children,
|
|
1711
|
+
className = ""
|
|
1712
|
+
}) {
|
|
1713
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1714
|
+
"div",
|
|
1715
|
+
{
|
|
1716
|
+
className: `nested-scroll-item flex-shrink-0 w-full h-full ${className}`,
|
|
1717
|
+
style: {
|
|
1718
|
+
scrollSnapAlign: "start",
|
|
1719
|
+
scrollSnapStop: "always"
|
|
1720
|
+
},
|
|
1721
|
+
children
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1336
1724
|
}
|
|
1337
|
-
|
|
1338
|
-
// hooks/useNavigation.ts
|
|
1339
|
-
var import_react15 = require("react");
|
|
1340
1725
|
function useNavigation() {
|
|
1341
1726
|
const goToView = useScrollStore((s) => s.goToView);
|
|
1342
1727
|
const goToNextAction = useScrollStore((s) => s.goToNext);
|
|
1343
1728
|
const goToPreviousAction = useScrollStore((s) => s.goToPrevious);
|
|
1344
1729
|
const setGlobalLock = useScrollStore((s) => s.setGlobalLock);
|
|
1345
|
-
const lockScroll =
|
|
1346
|
-
const unlockScroll =
|
|
1730
|
+
const lockScroll = react.useCallback(() => setGlobalLock(true), [setGlobalLock]);
|
|
1731
|
+
const unlockScroll = react.useCallback(() => setGlobalLock(false), [setGlobalLock]);
|
|
1347
1732
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1348
1733
|
const activeId = useScrollStore((s) => s.activeId);
|
|
1349
1734
|
const totalViews = useScrollStore((s) => s.totalViews);
|
|
@@ -1351,16 +1736,16 @@ function useNavigation() {
|
|
|
1351
1736
|
const isScrollLocked = useScrollStore((s) => s.isGlobalLocked);
|
|
1352
1737
|
const canNavigateNext = useScrollStore(selectCanNavigateNext);
|
|
1353
1738
|
const canNavigatePrevious = useScrollStore(selectCanNavigatePrevious);
|
|
1354
|
-
const goToNext =
|
|
1739
|
+
const goToNext = react.useCallback(() => {
|
|
1355
1740
|
return goToNextAction();
|
|
1356
1741
|
}, [goToNextAction]);
|
|
1357
|
-
const goToPrevious =
|
|
1742
|
+
const goToPrevious = react.useCallback(() => {
|
|
1358
1743
|
return goToPreviousAction();
|
|
1359
1744
|
}, [goToPreviousAction]);
|
|
1360
1745
|
const isFirstView = activeIndex === 0;
|
|
1361
1746
|
const isLastView = activeIndex === totalViews - 1;
|
|
1362
1747
|
const progress = totalViews > 1 ? activeIndex / (totalViews - 1) : 0;
|
|
1363
|
-
return
|
|
1748
|
+
return react.useMemo(
|
|
1364
1749
|
() => ({
|
|
1365
1750
|
// Acciones de navegación
|
|
1366
1751
|
goToView,
|
|
@@ -1430,18 +1815,15 @@ function useActiveViewProgress() {
|
|
|
1430
1815
|
viewType: activeView?.type
|
|
1431
1816
|
};
|
|
1432
1817
|
}
|
|
1433
|
-
|
|
1434
|
-
// hooks/useScrollAnalytics.ts
|
|
1435
|
-
var import_react16 = require("react");
|
|
1436
1818
|
function useScrollAnalytics(options = {}) {
|
|
1437
1819
|
const { onViewEnter, onViewExit, enabled = true } = options;
|
|
1438
1820
|
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1439
1821
|
const activeId = useScrollStore((s) => s.activeId);
|
|
1440
1822
|
const isTransitioning = useScrollStore((s) => s.isTransitioning);
|
|
1441
|
-
const enterTimeRef =
|
|
1442
|
-
const prevIndexRef =
|
|
1443
|
-
const prevIdRef =
|
|
1444
|
-
const createAnalytics =
|
|
1823
|
+
const enterTimeRef = react.useRef(Date.now());
|
|
1824
|
+
const prevIndexRef = react.useRef(activeIndex);
|
|
1825
|
+
const prevIdRef = react.useRef(activeId);
|
|
1826
|
+
const createAnalytics = react.useCallback((viewId, viewIndex, enterTime, isActive, exitTime = null) => ({
|
|
1445
1827
|
viewId,
|
|
1446
1828
|
viewIndex,
|
|
1447
1829
|
enterTime,
|
|
@@ -1449,7 +1831,7 @@ function useScrollAnalytics(options = {}) {
|
|
|
1449
1831
|
duration: exitTime ? (exitTime - enterTime) / 1e3 : 0,
|
|
1450
1832
|
isActive
|
|
1451
1833
|
}), []);
|
|
1452
|
-
|
|
1834
|
+
react.useEffect(() => {
|
|
1453
1835
|
if (!enabled) return;
|
|
1454
1836
|
if (!isTransitioning && (activeIndex !== prevIndexRef.current || activeId !== prevIdRef.current)) {
|
|
1455
1837
|
const now = Date.now();
|
|
@@ -1484,37 +1866,249 @@ function useScrollAnalytics(options = {}) {
|
|
|
1484
1866
|
getTimeInView: () => (Date.now() - enterTimeRef.current) / 1e3
|
|
1485
1867
|
};
|
|
1486
1868
|
}
|
|
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
|
-
}
|
|
1869
|
+
function useScrollLock() {
|
|
1870
|
+
const isLocked = useScrollStore((s) => s.isGlobalLocked);
|
|
1871
|
+
const setGlobalLock = useScrollStore((s) => s.setGlobalLock);
|
|
1872
|
+
const setViewExplicitLock = useScrollStore((s) => s.setViewExplicitLock);
|
|
1873
|
+
const lock = react.useCallback(() => {
|
|
1874
|
+
setGlobalLock(true);
|
|
1875
|
+
}, [setGlobalLock]);
|
|
1876
|
+
const unlock = react.useCallback(() => {
|
|
1877
|
+
setGlobalLock(false);
|
|
1878
|
+
}, [setGlobalLock]);
|
|
1879
|
+
const toggle = react.useCallback(() => {
|
|
1880
|
+
setGlobalLock(!isLocked);
|
|
1881
|
+
}, [setGlobalLock, isLocked]);
|
|
1882
|
+
const lockView = react.useCallback((viewId) => {
|
|
1883
|
+
setViewExplicitLock(viewId, "locked");
|
|
1884
|
+
}, [setViewExplicitLock]);
|
|
1885
|
+
const unlockView = react.useCallback((viewId) => {
|
|
1886
|
+
setViewExplicitLock(viewId, "unlocked");
|
|
1887
|
+
}, [setViewExplicitLock]);
|
|
1888
|
+
return {
|
|
1889
|
+
isLocked,
|
|
1890
|
+
lock,
|
|
1891
|
+
unlock,
|
|
1892
|
+
toggle,
|
|
1893
|
+
lockView,
|
|
1894
|
+
unlockView
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
var DEFAULT_CONFIG4 = {
|
|
1898
|
+
speed: 0.5,
|
|
1899
|
+
direction: "vertical",
|
|
1900
|
+
offset: 0,
|
|
1901
|
+
easing: "linear"
|
|
1902
|
+
};
|
|
1903
|
+
var easingFunctions = {
|
|
1904
|
+
linear: (t) => t,
|
|
1905
|
+
easeOut: (t) => 1 - Math.pow(1 - t, 2),
|
|
1906
|
+
easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
|
|
1907
|
+
};
|
|
1908
|
+
function useParallax(viewId, config = {}) {
|
|
1909
|
+
const mergedConfig = { ...DEFAULT_CONFIG4, ...config };
|
|
1910
|
+
const { speed, direction, offset, easing } = mergedConfig;
|
|
1911
|
+
const views = useScrollStore((s) => s.views);
|
|
1912
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1913
|
+
useScrollStore((s) => s.globalProgress);
|
|
1914
|
+
const result = react.useMemo(() => {
|
|
1915
|
+
const view = views.find((v) => v.id === viewId);
|
|
1916
|
+
if (!view) {
|
|
1917
|
+
return {
|
|
1918
|
+
transform: 0,
|
|
1919
|
+
style: {},
|
|
1920
|
+
progress: 0
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
const viewOffset = view.index - activeIndex;
|
|
1924
|
+
const internalProgress = view.isActive ? view.progress : 0;
|
|
1925
|
+
const combinedProgress = viewOffset + internalProgress;
|
|
1926
|
+
const easingFn = easingFunctions[easing];
|
|
1927
|
+
const easedProgress = easingFn(Math.abs(combinedProgress)) * Math.sign(combinedProgress);
|
|
1928
|
+
const maxDistance = 100;
|
|
1929
|
+
const transformValue = easedProgress * maxDistance * speed + offset;
|
|
1930
|
+
const transformProperty = direction === "vertical" ? `translateY(${transformValue}px)` : `translateX(${transformValue}px)`;
|
|
1931
|
+
const style = {
|
|
1932
|
+
transform: transformProperty,
|
|
1933
|
+
willChange: "transform"
|
|
1934
|
+
};
|
|
1935
|
+
return {
|
|
1936
|
+
transform: transformValue,
|
|
1937
|
+
style,
|
|
1938
|
+
progress: combinedProgress
|
|
1939
|
+
};
|
|
1940
|
+
}, [viewId, views, activeIndex, speed, direction, offset, easing]);
|
|
1941
|
+
return result;
|
|
1942
|
+
}
|
|
1943
|
+
function useActiveParallax(config = {}) {
|
|
1944
|
+
const mergedConfig = { ...DEFAULT_CONFIG4, ...config };
|
|
1945
|
+
const { speed, direction, offset, easing } = mergedConfig;
|
|
1946
|
+
const globalProgress = useScrollStore((s) => s.globalProgress);
|
|
1947
|
+
const activeIndex = useScrollStore((s) => s.activeIndex);
|
|
1948
|
+
const views = useScrollStore((s) => s.views);
|
|
1949
|
+
return react.useMemo(() => {
|
|
1950
|
+
const activeView = views[activeIndex];
|
|
1951
|
+
const viewProgress = activeView?.progress ?? 0;
|
|
1952
|
+
const easingFn = easingFunctions[easing];
|
|
1953
|
+
const easedProgress = easingFn(viewProgress);
|
|
1954
|
+
const maxDistance = 100;
|
|
1955
|
+
const transformValue = easedProgress * maxDistance * speed + offset;
|
|
1956
|
+
const transformProperty = direction === "vertical" ? `translateY(${transformValue}px)` : `translateX(${transformValue}px)`;
|
|
1957
|
+
const style = {
|
|
1958
|
+
transform: transformProperty,
|
|
1959
|
+
willChange: "transform"
|
|
1960
|
+
};
|
|
1961
|
+
return {
|
|
1962
|
+
transform: transformValue,
|
|
1963
|
+
style,
|
|
1964
|
+
progress: viewProgress
|
|
1965
|
+
};
|
|
1966
|
+
}, [globalProgress, activeIndex, views, speed, direction, offset, easing]);
|
|
1967
|
+
}
|
|
1968
|
+
function useSnapPoints(options) {
|
|
1969
|
+
const { viewId, points, snapThreshold = 0.1, smoothScroll = true } = options;
|
|
1970
|
+
const scrollContainerRef = react.useRef(null);
|
|
1971
|
+
const lastActivePointRef = react.useRef(null);
|
|
1972
|
+
const views = useScrollStore((s) => s.views);
|
|
1973
|
+
const setActiveSnapPoint = useScrollStore((s) => s.setActiveSnapPoint);
|
|
1974
|
+
const view = react.useMemo(() => views.find((v) => v.id === viewId), [views, viewId]);
|
|
1975
|
+
const viewProgress = view?.progress ?? 0;
|
|
1976
|
+
view?.activeSnapPointId ?? null;
|
|
1977
|
+
const sortedPoints = react.useMemo(
|
|
1978
|
+
() => [...points].sort((a, b) => a.position - b.position),
|
|
1979
|
+
[points]
|
|
1980
|
+
);
|
|
1981
|
+
const activePointData = react.useMemo(() => {
|
|
1982
|
+
if (sortedPoints.length === 0) {
|
|
1983
|
+
return { point: null, index: -1 };
|
|
1984
|
+
}
|
|
1985
|
+
let closestIndex = 0;
|
|
1986
|
+
let closestDistance = Math.abs(viewProgress - sortedPoints[0].position);
|
|
1987
|
+
for (let i = 1; i < sortedPoints.length; i++) {
|
|
1988
|
+
const distance = Math.abs(viewProgress - sortedPoints[i].position);
|
|
1989
|
+
if (distance < closestDistance) {
|
|
1990
|
+
closestDistance = distance;
|
|
1991
|
+
closestIndex = i;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
if (closestDistance <= snapThreshold) {
|
|
1995
|
+
return { point: sortedPoints[closestIndex], index: closestIndex };
|
|
1996
|
+
}
|
|
1997
|
+
for (let i = sortedPoints.length - 1; i >= 0; i--) {
|
|
1998
|
+
if (viewProgress >= sortedPoints[i].position - snapThreshold) {
|
|
1999
|
+
return { point: sortedPoints[i], index: i };
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return { point: sortedPoints[0], index: 0 };
|
|
2003
|
+
}, [viewProgress, sortedPoints, snapThreshold]);
|
|
2004
|
+
react.useEffect(() => {
|
|
2005
|
+
const newPointId = activePointData.point?.id ?? null;
|
|
2006
|
+
if (newPointId !== lastActivePointRef.current) {
|
|
2007
|
+
if (lastActivePointRef.current) {
|
|
2008
|
+
const prevPoint = sortedPoints.find((p) => p.id === lastActivePointRef.current);
|
|
2009
|
+
prevPoint?.onLeave?.();
|
|
2010
|
+
}
|
|
2011
|
+
setActiveSnapPoint(viewId, newPointId);
|
|
2012
|
+
if (newPointId) {
|
|
2013
|
+
const newPoint = sortedPoints.find((p) => p.id === newPointId);
|
|
2014
|
+
newPoint?.onReach?.();
|
|
2015
|
+
}
|
|
2016
|
+
lastActivePointRef.current = newPointId;
|
|
2017
|
+
}
|
|
2018
|
+
}, [activePointData.point?.id, viewId, sortedPoints, setActiveSnapPoint]);
|
|
2019
|
+
const goToPoint = react.useCallback((pointId) => {
|
|
2020
|
+
const point = sortedPoints.find((p) => p.id === pointId);
|
|
2021
|
+
if (!point || !scrollContainerRef.current) return;
|
|
2022
|
+
const container = scrollContainerRef.current;
|
|
2023
|
+
const maxScroll = container.scrollHeight - container.clientHeight;
|
|
2024
|
+
const targetScroll = point.position * maxScroll;
|
|
2025
|
+
container.scrollTo({
|
|
2026
|
+
top: targetScroll,
|
|
2027
|
+
behavior: smoothScroll ? "smooth" : "auto"
|
|
2028
|
+
});
|
|
2029
|
+
}, [sortedPoints, smoothScroll]);
|
|
2030
|
+
const goToNextPoint = react.useCallback(() => {
|
|
2031
|
+
const nextIndex = Math.min(activePointData.index + 1, sortedPoints.length - 1);
|
|
2032
|
+
const nextPoint = sortedPoints[nextIndex];
|
|
2033
|
+
if (nextPoint) {
|
|
2034
|
+
goToPoint(nextPoint.id);
|
|
2035
|
+
}
|
|
2036
|
+
}, [activePointData.index, sortedPoints, goToPoint]);
|
|
2037
|
+
const goToPrevPoint = react.useCallback(() => {
|
|
2038
|
+
const prevIndex = Math.max(activePointData.index - 1, 0);
|
|
2039
|
+
const prevPoint = sortedPoints[prevIndex];
|
|
2040
|
+
if (prevPoint) {
|
|
2041
|
+
goToPoint(prevPoint.id);
|
|
2042
|
+
}
|
|
2043
|
+
}, [activePointData.index, sortedPoints, goToPoint]);
|
|
2044
|
+
const pointsWithState = react.useMemo(
|
|
2045
|
+
() => sortedPoints.map((point) => ({
|
|
2046
|
+
...point,
|
|
2047
|
+
isActive: point.id === activePointData.point?.id
|
|
2048
|
+
})),
|
|
2049
|
+
[sortedPoints, activePointData.point?.id]
|
|
2050
|
+
);
|
|
2051
|
+
return {
|
|
2052
|
+
activePoint: activePointData.point,
|
|
2053
|
+
activeIndex: activePointData.index,
|
|
2054
|
+
goToPoint,
|
|
2055
|
+
goToNextPoint,
|
|
2056
|
+
goToPrevPoint,
|
|
2057
|
+
points: pointsWithState,
|
|
2058
|
+
progress: viewProgress
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function createSnapPoints(positions, options) {
|
|
2062
|
+
const { prefix = "snap", labels } = options ?? {};
|
|
2063
|
+
return positions.map((position, index) => ({
|
|
2064
|
+
id: `${prefix}-${index}`,
|
|
2065
|
+
position,
|
|
2066
|
+
label: labels?.[index] ?? `Section ${index + 1}`
|
|
2067
|
+
}));
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
exports.AriaLiveRegion = AriaLiveRegion;
|
|
2071
|
+
exports.ControlledView = ControlledView;
|
|
2072
|
+
exports.DEFAULT_PROGRESS_DEBOUNCE = DEFAULT_PROGRESS_DEBOUNCE;
|
|
2073
|
+
exports.DEFAULT_TRANSITION_DURATION = DEFAULT_TRANSITION_DURATION;
|
|
2074
|
+
exports.DEFAULT_TRANSITION_EASING = DEFAULT_TRANSITION_EASING;
|
|
2075
|
+
exports.FullView = FullView;
|
|
2076
|
+
exports.LazyView = LazyView;
|
|
2077
|
+
exports.NAVIGATION_COOLDOWN = NAVIGATION_COOLDOWN;
|
|
2078
|
+
exports.NAV_THRESHOLDS = NAV_THRESHOLDS;
|
|
2079
|
+
exports.NestedScrollItem = NestedScrollItem;
|
|
2080
|
+
exports.NestedScrollView = NestedScrollView;
|
|
2081
|
+
exports.ScrollContainer = ScrollContainer;
|
|
2082
|
+
exports.ScrollDebugOverlay = ScrollDebugOverlay;
|
|
2083
|
+
exports.ScrollLockedView = ScrollLockedView;
|
|
2084
|
+
exports.createSnapPoints = createSnapPoints;
|
|
2085
|
+
exports.selectActiveView = selectActiveView;
|
|
2086
|
+
exports.selectActiveViewProgress = selectActiveViewProgress;
|
|
2087
|
+
exports.selectCanNavigateNext = selectCanNavigateNext;
|
|
2088
|
+
exports.selectCanNavigatePrevious = selectCanNavigatePrevious;
|
|
2089
|
+
exports.selectGlobalProgress = selectGlobalProgress;
|
|
2090
|
+
exports.selectIsAutoScrolling = selectIsAutoScrolling;
|
|
2091
|
+
exports.useActiveParallax = useActiveParallax;
|
|
2092
|
+
exports.useActiveViewProgress = useActiveViewProgress;
|
|
2093
|
+
exports.useAutoScroll = useAutoScroll;
|
|
2094
|
+
exports.useDragHandler = useDragHandler;
|
|
2095
|
+
exports.useFocusManagement = useFocusManagement;
|
|
2096
|
+
exports.useGestureConfig = useGestureConfig;
|
|
2097
|
+
exports.useGlobalProgress = useGlobalProgress;
|
|
2098
|
+
exports.useHashSync = useHashSync;
|
|
2099
|
+
exports.useInfiniteScroll = useInfiniteScroll;
|
|
2100
|
+
exports.useKeyboardHandler = useKeyboardHandler;
|
|
2101
|
+
exports.useMetricsReporter = useMetricsReporter;
|
|
2102
|
+
exports.useNavigation = useNavigation;
|
|
2103
|
+
exports.useParallax = useParallax;
|
|
2104
|
+
exports.usePreload = usePreload;
|
|
2105
|
+
exports.useScrollAnalytics = useScrollAnalytics;
|
|
2106
|
+
exports.useScrollLock = useScrollLock;
|
|
2107
|
+
exports.useScrollStore = useScrollStore;
|
|
2108
|
+
exports.useScrollSystem = useScrollSystem;
|
|
2109
|
+
exports.useSnapPoints = useSnapPoints;
|
|
2110
|
+
exports.useTouchHandler = useTouchHandler;
|
|
2111
|
+
exports.useViewControl = useViewControl;
|
|
2112
|
+
exports.useViewProgress = useViewProgress;
|
|
2113
|
+
exports.useViewRegistration = useViewRegistration;
|
|
2114
|
+
exports.useWheelHandler = useWheelHandler;
|