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.
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 +878 -284
  67. package/dist/index.mjs +817 -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) {
@@ -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 = (0, import_react2.useRef)(null);
355
- const touchStartTime = (0, import_react2.useRef)(0);
356
- (0, import_react2.useEffect)(() => {
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
- (0, import_react3.useEffect)(() => {
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 = (0, import_react4.useRef)(false);
466
- (0, import_react4.useEffect)(() => {
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
- (0, import_react4.useEffect)(() => {
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
- (0, import_react4.useEffect)(() => {
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] = (0, import_react5.useState)({
543
+ const [dragState, setDragState] = react.useState({
534
544
  isDragging: false,
535
545
  dragOffset: 0,
536
546
  dragDirection: null
537
547
  });
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) => {
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
- (0, import_react5.useEffect)(() => {
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 = (0, import_react6.useRef)(activeIndex);
643
- (0, import_react6.useEffect)(() => {
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 = (0, import_react7.useCallback)(() => {
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 = (0, import_react7.useCallback)(() => {
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 = (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)(() => ({
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 = (0, import_react8.useRef)(null);
1043
+ const containerRef = react.useRef(null);
1044
+ const isFirstRender = react.useRef(true);
787
1045
  const isBrowser = typeof window !== "undefined";
788
- const [reducedMotion, setReducedMotion] = (0, import_react8.useState)(false);
789
- (0, import_react8.useEffect)(() => {
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
- const totalViews = useScrollStore((s) => s.totalViews);
801
- const isInitialized = useScrollStore((s) => s.isInitialized);
802
- const prevIndexRef = (0, import_react8.useRef)(activeIndex);
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
- (0, import_react8.useEffect)(() => {
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
- (0, import_react8.useEffect)(() => {
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 = (0, import_react8.useMemo)(() => {
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: dragState.isDragging ? "none" : effectiveDuration > 0 ? `transform ${effectiveDuration}ms ${transitionEasing}` : "none",
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__ */ (0, import_jsx_runtime.jsx)(
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
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scroll-wrapper", style: wrapperStyle, children })
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 = (0, import_react9.useRef)({
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 = (0, import_react9.useRef)(false);
893
- const wasTransitioningRef = (0, import_react9.useRef)(false);
894
- const configRef = (0, import_react9.useRef)(config);
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
- (0, import_react9.useEffect)(() => {
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
- (0, import_react9.useEffect)(() => {
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
- (0, import_react9.useEffect)(() => {
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 = (0, import_react10.useMemo)(
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__ */ (0, import_jsx_runtime2.jsx)(
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 = (0, import_react11.useRef)(null);
1278
+ const scrollRef = react.useRef(null);
1005
1279
  const updateMetrics = useScrollStore((s) => s.updateViewMetrics);
1006
- const measureAndReport = (0, import_react11.useCallback)(() => {
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 = (0, import_react11.useMemo)(
1295
+ const throttledMeasure = react.useMemo(
1022
1296
  () => throttle(measureAndReport, throttleMs),
1023
1297
  [measureAndReport, throttleMs]
1024
1298
  );
1025
- (0, import_react11.useEffect)(() => {
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
- (0, import_react11.useEffect)(() => {
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__ */ (0, import_jsx_runtime3.jsx)(
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__ */ (0, import_jsx_runtime3.jsx)(
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 = (0, import_react12.useMemo)(
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
- (0, import_react12.useEffect)(() => {
1415
+ react.useEffect(() => {
1149
1416
  const lockState = canProceed ? "unlocked" : "locked";
1150
1417
  setExplicitLock(id, lockState);
1151
1418
  }, [id, canProceed, setExplicitLock]);
1152
- const overflowClasses = (0, import_react12.useMemo)(() => {
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__ */ (0, import_jsx_runtime4.jsx)(
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__ */ (0, import_jsx_runtime4.jsx)(
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 (0, import_react12.useMemo)(
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__ */ (0, import_jsx_runtime5.jsxs)(
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__ */ (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" }),
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__ */ (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 })
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__ */ (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 })
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__ */ (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) })
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__ */ (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: [
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__ */ (0, import_jsx_runtime5.jsx)("span", { className: valueColor, children: displayValue })
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] = (0, import_react13.useState)("");
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
- (0, import_react13.useEffect)(() => {
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__ */ (0, import_jsx_runtime6.jsx)(
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 = (0, import_react14.useMemo)(() => {
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__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: placeholder });
1589
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: placeholder });
1334
1590
  }
1335
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
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 = (0, import_react15.useCallback)(() => setGlobalLock(true), [setGlobalLock]);
1346
- const unlockScroll = (0, import_react15.useCallback)(() => setGlobalLock(false), [setGlobalLock]);
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 = (0, import_react15.useCallback)(() => {
1739
+ const goToNext = react.useCallback(() => {
1355
1740
  return goToNextAction();
1356
1741
  }, [goToNextAction]);
1357
- const goToPrevious = (0, import_react15.useCallback)(() => {
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 (0, import_react15.useMemo)(
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 = (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) => ({
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
- (0, import_react16.useEffect)(() => {
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
- // 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
- });
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;