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.
Files changed (79) hide show
  1. package/README.md +138 -3
  2. package/dist/components/AriaLiveRegion.d.ts +18 -0
  3. package/dist/components/AriaLiveRegion.d.ts.map +1 -0
  4. package/dist/components/ControlledView.d.ts +16 -0
  5. package/dist/components/ControlledView.d.ts.map +1 -0
  6. package/dist/components/FullView.d.ts +20 -0
  7. package/dist/components/FullView.d.ts.map +1 -0
  8. package/dist/components/LazyView.d.ts +33 -0
  9. package/dist/components/LazyView.d.ts.map +1 -0
  10. package/dist/components/NestedScrollView.d.ts +38 -0
  11. package/dist/components/NestedScrollView.d.ts.map +1 -0
  12. package/dist/components/ScrollContainer.d.ts +9 -0
  13. package/dist/components/ScrollContainer.d.ts.map +1 -0
  14. package/dist/components/ScrollDebugOverlay.d.ts +17 -0
  15. package/dist/components/ScrollDebugOverlay.d.ts.map +1 -0
  16. package/dist/components/ScrollLockedView.d.ts +10 -0
  17. package/dist/components/ScrollLockedView.d.ts.map +1 -0
  18. package/dist/components/index.d.ts +9 -0
  19. package/dist/components/index.d.ts.map +1 -0
  20. package/dist/constants.d.ts +17 -0
  21. package/dist/constants.d.ts.map +1 -0
  22. package/dist/hooks/index.d.ts +21 -0
  23. package/dist/hooks/index.d.ts.map +1 -0
  24. package/dist/hooks/useAutoScroll.d.ts +41 -0
  25. package/dist/hooks/useAutoScroll.d.ts.map +1 -0
  26. package/dist/hooks/useDragHandler.d.ts +28 -0
  27. package/dist/hooks/useDragHandler.d.ts.map +1 -0
  28. package/dist/hooks/useFocusManagement.d.ts +19 -0
  29. package/dist/hooks/useFocusManagement.d.ts.map +1 -0
  30. package/dist/hooks/useGestureConfig.d.ts +37 -0
  31. package/dist/hooks/useGestureConfig.d.ts.map +1 -0
  32. package/dist/hooks/useGlobalProgress.d.ts +36 -0
  33. package/dist/hooks/useGlobalProgress.d.ts.map +1 -0
  34. package/dist/hooks/useHashSync.d.ts +24 -0
  35. package/dist/hooks/useHashSync.d.ts.map +1 -0
  36. package/dist/hooks/useInfiniteScroll.d.ts +41 -0
  37. package/dist/hooks/useInfiniteScroll.d.ts.map +1 -0
  38. package/dist/hooks/useKeyboardHandler.d.ts +20 -0
  39. package/dist/hooks/useKeyboardHandler.d.ts.map +1 -0
  40. package/dist/hooks/useMetricsReporter.d.ts +23 -0
  41. package/dist/hooks/useMetricsReporter.d.ts.map +1 -0
  42. package/dist/hooks/useNavigation.d.ts +42 -0
  43. package/dist/hooks/useNavigation.d.ts.map +1 -0
  44. package/dist/hooks/useParallax.d.ts +33 -0
  45. package/dist/hooks/useParallax.d.ts.map +1 -0
  46. package/dist/hooks/usePreload.d.ts +31 -0
  47. package/dist/hooks/usePreload.d.ts.map +1 -0
  48. package/dist/hooks/useScrollAnalytics.d.ts +40 -0
  49. package/dist/hooks/useScrollAnalytics.d.ts.map +1 -0
  50. package/dist/hooks/useScrollLock.d.ts +40 -0
  51. package/dist/hooks/useScrollLock.d.ts.map +1 -0
  52. package/dist/hooks/useScrollSystem.d.ts +16 -0
  53. package/dist/hooks/useScrollSystem.d.ts.map +1 -0
  54. package/dist/hooks/useSnapPoints.d.ts +77 -0
  55. package/dist/hooks/useSnapPoints.d.ts.map +1 -0
  56. package/dist/hooks/useTouchHandler.d.ts +12 -0
  57. package/dist/hooks/useTouchHandler.d.ts.map +1 -0
  58. package/dist/hooks/useViewProgress.d.ts +22 -0
  59. package/dist/hooks/useViewProgress.d.ts.map +1 -0
  60. package/dist/hooks/useViewRegistration.d.ts +25 -0
  61. package/dist/hooks/useViewRegistration.d.ts.map +1 -0
  62. package/dist/hooks/useWheelHandler.d.ts +8 -0
  63. package/dist/hooks/useWheelHandler.d.ts.map +1 -0
  64. package/dist/index.d.ts +16 -2009
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +860 -284
  67. package/dist/index.mjs +799 -212
  68. package/dist/store/index.d.ts +2 -0
  69. package/dist/store/index.d.ts.map +1 -0
  70. package/dist/store/navigation.store.d.ts +23 -0
  71. package/dist/store/navigation.store.d.ts.map +1 -0
  72. package/dist/types/index.d.ts +315 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/dist/utils/index.d.ts +21 -0
  75. package/dist/utils/index.d.ts.map +1 -0
  76. package/dist/utils/normalizeWheel.d.ts +10 -0
  77. package/dist/utils/normalizeWheel.d.ts.map +1 -0
  78. package/package.json +4 -2
  79. package/dist/index.d.mts +0 -2010
package/dist/index.js CHANGED
@@ -1,65 +1,11 @@
1
- "use strict";
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
- // index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- AriaLiveRegion: () => AriaLiveRegion,
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 = (0, import_zustand.create)()(
108
- (0, import_middleware.subscribeWithSelector)((set, get) => ({
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
- return { views: newViews };
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 < state.totalViews - 1) {
187
- get().goToView(state.activeIndex + 1);
188
- return true;
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 > 0) {
204
- get().goToView(state.activeIndex - 1);
205
- return true;
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.goToView(state.activeIndex + 1);
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.goToView(state.activeIndex - 1);
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
- ...s,
229
- isTransitioning: true,
230
- activeIndex: targetIndex,
231
- activeId: s.views[targetIndex]?.id ?? null,
232
- views: s.views.map((v) => {
233
- if (v.index === targetIndex) return { ...v, isActive: true };
234
- if (v.index === s.activeIndex) return { ...v, isActive: false };
235
- return v;
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
- // hooks/useWheelHandler.ts
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 = (0, import_react.useRef)(0);
312
- const lastScrollTime = (0, import_react.useRef)(0);
313
- const isTransitioning = useScrollStore((s) => s.isTransitioning);
314
- (0, import_react.useEffect)(() => {
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 = (0, import_react2.useRef)(null);
355
- const touchStartTime = (0, import_react2.useRef)(0);
356
- (0, import_react2.useEffect)(() => {
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
- (0, import_react3.useEffect)(() => {
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 = (0, import_react4.useRef)(false);
466
- (0, import_react4.useEffect)(() => {
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
- (0, import_react4.useEffect)(() => {
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
- (0, import_react4.useEffect)(() => {
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] = (0, import_react5.useState)({
525
+ const [dragState, setDragState] = react.useState({
534
526
  isDragging: false,
535
527
  dragOffset: 0,
536
528
  dragDirection: null
537
529
  });
538
- const touchStartRef = (0, import_react5.useRef)(null);
539
- const lastMoveRef = (0, import_react5.useRef)(null);
540
- const rafRef = (0, import_react5.useRef)(null);
541
- const updateDragState = (0, import_react5.useCallback)((newState) => {
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
- (0, import_react5.useEffect)(() => {
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 = (0, import_react6.useRef)(activeIndex);
643
- (0, import_react6.useEffect)(() => {
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 = (0, import_react7.useCallback)(() => {
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 = (0, import_react7.useCallback)(() => {
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 = (0, import_react7.useCallback)(() => activeIndex, [activeIndex]);
697
- const getProgress = (0, import_react7.useCallback)(() => globalProgress, [globalProgress]);
698
- const getActiveViewProgress = (0, import_react7.useCallback)(() => activeViewProgress, [activeViewProgress]);
699
- const isLocked = (0, import_react7.useCallback)(() => isGlobalLocked || isTransitioning || !canNavigateNext, [isGlobalLocked, isTransitioning, canNavigateNext]);
700
- return (0, import_react7.useMemo)(() => ({
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 = (0, import_react8.useRef)(null);
1025
+ const containerRef = react.useRef(null);
1026
+ const isFirstRender = react.useRef(true);
787
1027
  const isBrowser = typeof window !== "undefined";
788
- const [reducedMotion, setReducedMotion] = (0, import_react8.useState)(false);
789
- (0, import_react8.useEffect)(() => {
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
- const totalViews = useScrollStore((s) => s.totalViews);
801
- const isInitialized = useScrollStore((s) => s.isInitialized);
802
- const prevIndexRef = (0, import_react8.useRef)(activeIndex);
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
- (0, import_react8.useEffect)(() => {
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
- (0, import_react8.useEffect)(() => {
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 = (0, import_react8.useMemo)(() => {
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: dragState.isDragging ? "none" : effectiveDuration > 0 ? `transform ${effectiveDuration}ms ${transitionEasing}` : "none",
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__ */ (0, import_jsx_runtime.jsx)(
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
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scroll-wrapper", style: wrapperStyle, children })
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 = (0, import_react9.useRef)({
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 = (0, import_react9.useRef)(false);
893
- const wasTransitioningRef = (0, import_react9.useRef)(false);
894
- const configRef = (0, import_react9.useRef)(config);
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
- (0, import_react9.useEffect)(() => {
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
- (0, import_react9.useEffect)(() => {
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
- (0, import_react9.useEffect)(() => {
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 = (0, import_react10.useMemo)(
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__ */ (0, import_jsx_runtime2.jsx)(
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 = (0, import_react11.useRef)(null);
1260
+ const scrollRef = react.useRef(null);
1005
1261
  const updateMetrics = useScrollStore((s) => s.updateViewMetrics);
1006
- const measureAndReport = (0, import_react11.useCallback)(() => {
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 = (0, import_react11.useMemo)(
1277
+ const throttledMeasure = react.useMemo(
1022
1278
  () => throttle(measureAndReport, throttleMs),
1023
1279
  [measureAndReport, throttleMs]
1024
1280
  );
1025
- (0, import_react11.useEffect)(() => {
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
- (0, import_react11.useEffect)(() => {
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__ */ (0, import_jsx_runtime3.jsx)(
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__ */ (0, import_jsx_runtime3.jsx)(
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 = (0, import_react12.useMemo)(
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
- (0, import_react12.useEffect)(() => {
1397
+ react.useEffect(() => {
1149
1398
  const lockState = canProceed ? "unlocked" : "locked";
1150
1399
  setExplicitLock(id, lockState);
1151
1400
  }, [id, canProceed, setExplicitLock]);
1152
- const overflowClasses = (0, import_react12.useMemo)(() => {
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__ */ (0, import_jsx_runtime4.jsx)(
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__ */ (0, import_jsx_runtime4.jsx)(
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 (0, import_react12.useMemo)(
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__ */ (0, import_jsx_runtime5.jsxs)(
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__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "text-green-300 font-bold mb-2 flex items-center gap-2", children: [
1226
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-2 h-2 rounded-full bg-green-500 animate-pulse" }),
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__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1 mb-3", children: [
1230
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "initialized", value: isInitialized }),
1231
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "activeIndex", value: `${activeIndex} / ${totalViews - 1}` }),
1232
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "transitioning", value: isTransitioning }),
1233
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "globalLocked", value: isGlobalLocked })
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__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
1236
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-green-300/70 mb-1", children: "Active View" }),
1237
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "id", value: activeView.id }),
1238
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "type", value: activeView.type }),
1239
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "capability", value: activeView.capability }),
1240
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "navigation", value: activeView.navigation }),
1241
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "progress", value: `${(activeView.progress * 100).toFixed(0)}%` }),
1242
- activeView.explicitLock && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "explicitLock", value: activeView.explicitLock, highlight: true })
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__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-green-300/70 mb-1", children: "Metrics" }),
1246
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "scrollHeight", value: activeView.metrics.scrollHeight }),
1247
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "clientHeight", value: activeView.metrics.clientHeight }),
1248
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "scrollTop", value: Math.round(activeView.metrics.scrollTop) })
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__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-between gap-4", children: [
1262
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-green-500/70", children: [
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__ */ (0, import_jsx_runtime5.jsx)("span", { className: valueColor, children: displayValue })
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] = (0, import_react13.useState)("");
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
- (0, import_react13.useEffect)(() => {
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__ */ (0, import_jsx_runtime6.jsx)(
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 = (0, import_react14.useMemo)(() => {
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__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: placeholder });
1571
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: placeholder });
1334
1572
  }
1335
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
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 = (0, import_react15.useCallback)(() => setGlobalLock(true), [setGlobalLock]);
1346
- const unlockScroll = (0, import_react15.useCallback)(() => setGlobalLock(false), [setGlobalLock]);
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 = (0, import_react15.useCallback)(() => {
1721
+ const goToNext = react.useCallback(() => {
1355
1722
  return goToNextAction();
1356
1723
  }, [goToNextAction]);
1357
- const goToPrevious = (0, import_react15.useCallback)(() => {
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 (0, import_react15.useMemo)(
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 = (0, import_react16.useRef)(Date.now());
1442
- const prevIndexRef = (0, import_react16.useRef)(activeIndex);
1443
- const prevIdRef = (0, import_react16.useRef)(activeId);
1444
- const createAnalytics = (0, import_react16.useCallback)((viewId, viewIndex, enterTime, isActive, exitTime = null) => ({
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
- (0, import_react16.useEffect)(() => {
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
- // Annotate the CommonJS export names for ESM import in node:
1488
- 0 && (module.exports = {
1489
- AriaLiveRegion,
1490
- ControlledView,
1491
- DEFAULT_PROGRESS_DEBOUNCE,
1492
- DEFAULT_TRANSITION_DURATION,
1493
- DEFAULT_TRANSITION_EASING,
1494
- FullView,
1495
- LazyView,
1496
- NAVIGATION_COOLDOWN,
1497
- NAV_THRESHOLDS,
1498
- ScrollContainer,
1499
- ScrollDebugOverlay,
1500
- ScrollLockedView,
1501
- selectActiveView,
1502
- selectActiveViewProgress,
1503
- selectCanNavigateNext,
1504
- selectCanNavigatePrevious,
1505
- useActiveViewProgress,
1506
- useDragHandler,
1507
- useFocusManagement,
1508
- useHashSync,
1509
- useKeyboardHandler,
1510
- useMetricsReporter,
1511
- useNavigation,
1512
- useScrollAnalytics,
1513
- useScrollStore,
1514
- useScrollSystem,
1515
- useTouchHandler,
1516
- useViewControl,
1517
- useViewProgress,
1518
- useViewRegistration,
1519
- useWheelHandler
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;