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.d.mts DELETED
@@ -1,2010 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as React$1 from 'react';
3
- import React__default from 'react';
4
- import * as zustand from 'zustand';
5
-
6
- /**
7
- * Scroll System - Type Definitions
8
- * ========================================
9
- * Sistema de scroll tipo TikTok con arquitectura determinística.
10
- * El Store es la única fuente de verdad.
11
- */
12
- /**
13
- * Capacidad física de scroll de una vista.
14
- * Distingue si el contenido cabe o no en el viewport.
15
- */
16
- type ScrollCapability = "none" | "internal";
17
- /**
18
- * Estado de permiso de navegación.
19
- */
20
- type NavigationState = "locked" | "unlocked";
21
- /**
22
- * Métricas crudas del DOM reportadas por las vistas.
23
- */
24
- interface ViewMetrics {
25
- scrollHeight: number;
26
- clientHeight: number;
27
- scrollTop: number;
28
- }
29
- type UserIntentionType = "scroll" | "navigate";
30
- type UserDirection = "up" | "down" | "left" | "right";
31
- interface UserIntention {
32
- type: UserIntentionType;
33
- direction: UserDirection;
34
- strength: number;
35
- origin: "wheel" | "touch" | "keyboard" | "programmatic";
36
- }
37
- interface ScrollSystemAPI {
38
- goToNext: () => boolean;
39
- goToPrev: () => boolean;
40
- goTo: (index: number | string) => void;
41
- getCurrentIndex: () => number;
42
- getProgress: () => number;
43
- getActiveViewProgress: () => number;
44
- isLocked: () => boolean;
45
- canGoNext: boolean;
46
- canGoPrev: boolean;
47
- activeIndex: number;
48
- activeId: string | null;
49
- activeViewType: ViewType | null;
50
- totalViews: number;
51
- }
52
- type ViewType = "full" | "scroll-locked" | "controlled";
53
- type ScrollDirection = "vertical" | "horizontal" | "none";
54
- interface BaseViewConfig {
55
- id: string;
56
- type: ViewType;
57
- index?: number;
58
- meta?: Record<string, unknown>;
59
- }
60
- interface FullViewConfig extends BaseViewConfig {
61
- type: "full";
62
- }
63
- interface ScrollLockedViewConfig extends BaseViewConfig {
64
- type: "scroll-locked";
65
- scrollDirection: ScrollDirection;
66
- scrollEndThreshold?: number;
67
- }
68
- interface ControlledViewConfig extends BaseViewConfig {
69
- type: "controlled";
70
- scrollDirection?: ScrollDirection;
71
- allowInternalScroll?: boolean;
72
- allowGoBack?: boolean;
73
- }
74
- type ViewConfig = FullViewConfig | ScrollLockedViewConfig | ControlledViewConfig;
75
- interface ViewState {
76
- id: string;
77
- index: number;
78
- type: ViewType;
79
- isActive: boolean;
80
- capability: ScrollCapability;
81
- navigation: NavigationState;
82
- explicitLock: NavigationState | null;
83
- progress: number;
84
- metrics: ViewMetrics;
85
- config: ViewConfig;
86
- }
87
- interface ScrollSystemState {
88
- views: ViewState[];
89
- activeIndex: number;
90
- activeId: string | null;
91
- totalViews: number;
92
- isInitialized: boolean;
93
- isTransitioning: boolean;
94
- isGlobalLocked: boolean;
95
- isDragging: boolean;
96
- globalProgress: number;
97
- }
98
- interface ScrollSystemActions {
99
- initialize: () => void;
100
- registerView: (config: ViewConfig) => void;
101
- unregisterView: (id: string) => void;
102
- processIntention: (intention: UserIntention) => boolean;
103
- goToNext: () => void;
104
- goToPrevious: () => void;
105
- goToView: (indexOrId: number | string) => void;
106
- updateViewMetrics: (id: string, metrics: ViewMetrics) => void;
107
- setViewExplicitLock: (id: string, lock: NavigationState | null) => void;
108
- setGlobalLock: (locked: boolean) => void;
109
- setDragging: (dragging: boolean) => void;
110
- startTransition: () => void;
111
- endTransition: () => void;
112
- resetNavigationCooldown: () => void;
113
- }
114
- type ScrollSystemStore = ScrollSystemState & ScrollSystemActions;
115
- interface BaseViewProps {
116
- id: string;
117
- children: React.ReactNode;
118
- className?: string;
119
- onActivate?: () => void;
120
- onDeactivate?: () => void;
121
- /** Called when view starts entering (transition begins) */
122
- onEnterStart?: () => void;
123
- /** Called when view finishes entering (transition complete) */
124
- onEnterEnd?: () => void;
125
- /** Called when view starts exiting (transition begins) */
126
- onExitStart?: () => void;
127
- /** Called when view finishes exiting (transition complete) */
128
- onExitEnd?: () => void;
129
- }
130
- interface FullViewProps extends BaseViewProps {
131
- meta?: Record<string, unknown>;
132
- }
133
- interface ScrollLockedViewProps extends BaseViewProps {
134
- scrollDirection?: ScrollDirection;
135
- scrollEndThreshold?: number;
136
- onScrollProgress?: (progress: number) => void;
137
- }
138
- interface ControlledViewProps extends BaseViewProps {
139
- scrollDirection?: ScrollDirection;
140
- allowInternalScroll?: boolean;
141
- canProceed?: boolean;
142
- allowGoBack?: boolean;
143
- }
144
- interface ScrollContainerProps {
145
- children: React.ReactNode;
146
- className?: string;
147
- transitionDuration?: number;
148
- transitionEasing?: string;
149
- onViewChange?: (fromIndex: number, toIndex: number) => void;
150
- onInitialized?: () => void;
151
- /** Enable URL hash sync for deep linking (default: false) */
152
- enableHashSync?: boolean;
153
- /** Use pushState instead of replaceState for hash updates (default: false) */
154
- hashPushHistory?: boolean;
155
- /** Prefix for hash (e.g., "section-" creates "#section-hero") */
156
- hashPrefix?: string;
157
- /** Respect prefers-reduced-motion OS setting (default: true) */
158
- respectReducedMotion?: boolean;
159
- /** Enable focus management for screen readers (default: true) */
160
- enableFocusManagement?: boolean;
161
- /** Enable 1:1 drag physics for touch devices (default: false) */
162
- enableDragPhysics?: boolean;
163
- /** Scroll orientation: vertical or horizontal (default: "vertical") */
164
- orientation?: "vertical" | "horizontal";
165
- }
166
-
167
- declare function ScrollContainer$1({ children, className, transitionDuration, transitionEasing, onViewChange, onInitialized, enableHashSync, hashPushHistory, hashPrefix, respectReducedMotion, enableFocusManagement, enableDragPhysics, orientation, }: ScrollContainerProps): react_jsx_runtime.JSX.Element;
168
-
169
- /**
170
- * Vista que ocupa el 100% del viewport sin scroll interno.
171
- * Ideal para hero sections, splash screens, o secciones de impacto visual.
172
- *
173
- * @example
174
- * ```tsx
175
- * <FullView id="hero">
176
- * <h1>Welcome to PABRIX</h1>
177
- * </FullView>
178
- * ```
179
- */
180
- declare function FullView$1({ id, children, className, meta, onActivate, onDeactivate, onEnterStart, onEnterEnd, onExitStart, onExitEnd, }: FullViewProps): react_jsx_runtime.JSX.Element;
181
-
182
- declare function ScrollLockedView$1({ id, children, className, scrollDirection, scrollEndThreshold, onScrollProgress, onActivate, onDeactivate, onEnterStart, onEnterEnd, onExitStart, onExitEnd, }: ScrollLockedViewProps): react_jsx_runtime.JSX.Element;
183
-
184
- declare function ControlledView$1({ id, children, className, scrollDirection, allowInternalScroll, canProceed, allowGoBack, onActivate, onDeactivate, onEnterStart, onEnterEnd, onExitStart, onExitEnd, }: ControlledViewProps): react_jsx_runtime.JSX.Element;
185
- declare function useViewControl$1(viewId: string): {
186
- unlock: () => void;
187
- lock: () => void;
188
- goNext: () => void;
189
- goPrev: () => void;
190
- goTo: (to: number | string) => void;
191
- };
192
-
193
- /**
194
- * Scroll System - Debug Overlay
195
- * ===============================
196
- * Visual debugging component for the scroll system.
197
- * Shows internal state in real-time for development.
198
- *
199
- * Usage: <ScrollDebugOverlay /> inside ScrollContainer
200
- */
201
- interface ScrollDebugOverlayProps {
202
- /** Position of the overlay (default: "bottom-left") */
203
- position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
204
- /** Show/hide the overlay (default: true) */
205
- visible?: boolean;
206
- }
207
- declare function ScrollDebugOverlay$1({ position, visible, }: ScrollDebugOverlayProps): react_jsx_runtime.JSX.Element | null;
208
-
209
- /**
210
- * Scroll System - Aria Live Region
211
- * ==================================
212
- * Announces view changes to screen readers.
213
- */
214
- interface AriaLiveRegionProps {
215
- /** Custom announcement template. Use {viewIndex} and {viewId} as placeholders */
216
- template?: string;
217
- /** Politeness level for announcements */
218
- politeness?: "polite" | "assertive";
219
- }
220
- /**
221
- * Invisible component that announces view changes to screen readers.
222
- * Place inside ScrollContainer.
223
- */
224
- declare function AriaLiveRegion$1({ template, politeness, }: AriaLiveRegionProps): react_jsx_runtime.JSX.Element;
225
-
226
- interface LazyViewProps {
227
- /** The view ID to track */
228
- viewId: string;
229
- /** Number of views before/after active to render (default: 1) */
230
- buffer?: number;
231
- /** Content to render when lazy loading */
232
- children: React__default.ReactNode;
233
- /** Placeholder to show when not in range */
234
- placeholder?: React__default.ReactNode;
235
- }
236
- /**
237
- * Wraps view content to only render when within the active range.
238
- * Improves performance for apps with many views.
239
- *
240
- * @example
241
- * ```tsx
242
- * <FullView id="section-3">
243
- * <LazyView viewId="section-3">
244
- * <HeavyComponent />
245
- * </LazyView>
246
- * </FullView>
247
- * ```
248
- */
249
- declare function LazyView$1({ viewId, buffer, children, placeholder, }: LazyViewProps): react_jsx_runtime.JSX.Element;
250
-
251
- /**
252
- * PABRIX Scroll System - Navigation Hook
253
- * =======================================
254
- * Hook principal para interactuar con el sistema de navegación.
255
- * Provee una API limpia y tipada para controlar la navegación.
256
- */
257
- /**
258
- * Hook para acceder a la API de navegación del sistema de scroll
259
- *
260
- * @deprecated Use `useScrollSystem` instead. This hook will be removed in future versions.
261
- *
262
- * @example
263
- * ```tsx
264
- * const { goToView, goToNext, activeIndex } = useNavigation();
265
- *
266
- * // Navegar a una vista específica
267
- * goToView('hero');
268
- * goToView(2);
269
- *
270
- * // Navegar secuencialmente
271
- * goToNext();
272
- * goToPrevious();
273
- * ```
274
- */
275
- declare function useNavigation$1(): {
276
- goToView: (indexOrId: number | string) => void;
277
- goToNext: () => void;
278
- goToPrevious: () => void;
279
- lockScroll: () => void;
280
- unlockScroll: () => void;
281
- activeIndex: number;
282
- activeId: string | null;
283
- totalViews: number;
284
- isTransitioning: boolean;
285
- isScrollLocked: boolean;
286
- canNavigateNext: boolean;
287
- canNavigatePrevious: boolean;
288
- isFirstView: boolean;
289
- isLastView: boolean;
290
- progress: number;
291
- };
292
-
293
- var __defProp = Object.defineProperty;
294
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
295
- var __getOwnPropNames = Object.getOwnPropertyNames;
296
- var __hasOwnProp = Object.prototype.hasOwnProperty;
297
- var __export = (target, all) => {
298
- for (var name in all)
299
- __defProp(target, name, { get: all[name], enumerable: true });
300
- };
301
- var __copyProps = (to, from, except, desc) => {
302
- if (from && typeof from === "object" || typeof from === "function") {
303
- for (let key of __getOwnPropNames(from))
304
- if (!__hasOwnProp.call(to, key) && key !== except)
305
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
306
- }
307
- return to;
308
- };
309
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
310
-
311
- // index.ts
312
- var index_exports = {};
313
- __export(index_exports, {
314
- AriaLiveRegion: () => AriaLiveRegion,
315
- ControlledView: () => ControlledView,
316
- DEFAULT_PROGRESS_DEBOUNCE: () => DEFAULT_PROGRESS_DEBOUNCE$1,
317
- DEFAULT_TRANSITION_DURATION: () => DEFAULT_TRANSITION_DURATION$1,
318
- DEFAULT_TRANSITION_EASING: () => DEFAULT_TRANSITION_EASING$1,
319
- FullView: () => FullView,
320
- LazyView: () => LazyView,
321
- NAVIGATION_COOLDOWN: () => NAVIGATION_COOLDOWN$1,
322
- NAV_THRESHOLDS: () => NAV_THRESHOLDS$1,
323
- ScrollContainer: () => ScrollContainer,
324
- ScrollDebugOverlay: () => ScrollDebugOverlay,
325
- ScrollLockedView: () => ScrollLockedView,
326
- selectActiveView: () => selectActiveView$1,
327
- selectActiveViewProgress: () => selectActiveViewProgress$1,
328
- selectCanNavigateNext: () => selectCanNavigateNext$1,
329
- selectCanNavigatePrevious: () => selectCanNavigatePrevious$1,
330
- useActiveViewProgress: () => useActiveViewProgress$1,
331
- useDragHandler: () => useDragHandler$1,
332
- useFocusManagement: () => useFocusManagement$1,
333
- useHashSync: () => useHashSync$1,
334
- useKeyboardHandler: () => useKeyboardHandler$1,
335
- useMetricsReporter: () => useMetricsReporter$1,
336
- useNavigation: () => useNavigation,
337
- useScrollAnalytics: () => useScrollAnalytics$1,
338
- useScrollStore: () => useScrollStore$1,
339
- useScrollSystem: () => useScrollSystem$1,
340
- useTouchHandler: () => useTouchHandler$1,
341
- useViewControl: () => useViewControl,
342
- useViewProgress: () => useViewProgress$1,
343
- useViewRegistration: () => useViewRegistration$1,
344
- useWheelHandler: () => useWheelHandler$1
345
- });
346
- module.exports = __toCommonJS(index_exports);
347
-
348
- // components/ScrollContainer.tsx
349
- var import_react8 = require("react");
350
-
351
- // store/navigation.store.ts
352
- var import_zustand = require("zustand");
353
- var import_middleware = require("zustand/middleware");
354
-
355
- // constants.ts
356
- var DEFAULT_TRANSITION_DURATION$1 = 700;
357
- var DEFAULT_TRANSITION_EASING$1 = "cubic-bezier(0.645, 0.045, 0.355, 1.000)";
358
- var DEFAULT_PROGRESS_DEBOUNCE$1 = 16;
359
- var NAVIGATION_COOLDOWN$1 = 500;
360
- var NAV_THRESHOLDS$1 = {
361
- WHEEL: 60,
362
- // Acumulado de deltaY para disparar navegación
363
- TOUCH: 50
364
- // Píxeles de distancia para considerar swipe
365
- };
366
-
367
- // store/navigation.store.ts
368
- function evaluateStateMachine(capability, progress, viewType, explicitLock) {
369
- if (explicitLock) return explicitLock;
370
- if (capability === "none") return "unlocked";
371
- if (viewType === "full") return "unlocked";
372
- if (progress >= 0.99) return "unlocked";
373
- return "locked";
374
- }
375
- function calculateCapability(metrics) {
376
- if (metrics.scrollHeight - metrics.clientHeight <= 1) {
377
- return "none";
378
- }
379
- return "internal";
380
- }
381
- function calculateProgress(metrics) {
382
- const maxScroll = metrics.scrollHeight - metrics.clientHeight;
383
- if (maxScroll <= 1) return 1;
384
- return Math.max(0, Math.min(1, metrics.scrollTop / maxScroll));
385
- }
386
- var initialState = {
387
- views: [],
388
- activeIndex: 0,
389
- activeId: null,
390
- totalViews: 0,
391
- isInitialized: false,
392
- isTransitioning: false,
393
- isGlobalLocked: false,
394
- isDragging: false,
395
- globalProgress: 0
396
- };
397
- var lastNavigationTime = 0;
398
- var useScrollStore$1 = (0, import_zustand.create)()(
399
- (0, import_middleware.subscribeWithSelector)((set, get) => ({
400
- ...initialState,
401
- initialize: () => {
402
- const { views } = get();
403
- if (views.length > 0) {
404
- set({
405
- isInitialized: true,
406
- activeId: views[0]?.id ?? null,
407
- activeIndex: 0
408
- });
409
- }
410
- },
411
- registerView: (config) => {
412
- set((state) => {
413
- if (state.views.some((v) => v.id === config.id)) return state;
414
- const newIndex = state.views.length;
415
- const newView = {
416
- id: config.id,
417
- index: newIndex,
418
- type: config.type,
419
- isActive: newIndex === 0,
420
- capability: "none",
421
- navigation: "unlocked",
422
- explicitLock: null,
423
- progress: 0,
424
- metrics: { scrollHeight: 0, clientHeight: 0, scrollTop: 0 },
425
- config
426
- };
427
- const newViews = [...state.views, newView];
428
- return {
429
- views: newViews,
430
- totalViews: newViews.length,
431
- activeId: state.activeId ?? newView.id
432
- };
433
- });
434
- },
435
- unregisterView: (id) => {
436
- set((state) => {
437
- const newViews = state.views.filter((v) => v.id !== id).map((v, idx) => ({ ...v, index: idx }));
438
- const newActiveIndex = Math.min(state.activeIndex, newViews.length - 1);
439
- return {
440
- views: newViews,
441
- totalViews: newViews.length,
442
- activeIndex: Math.max(0, newActiveIndex),
443
- activeId: newViews[newActiveIndex]?.id ?? null
444
- };
445
- });
446
- },
447
- updateViewMetrics: (id, metrics) => {
448
- set((state) => {
449
- const viewIndex = state.views.findIndex((v) => v.id === id);
450
- if (viewIndex === -1) return state;
451
- const view = state.views[viewIndex];
452
- const capability = calculateCapability(metrics);
453
- const progress = calculateProgress(metrics);
454
- const navigation = evaluateStateMachine(capability, progress, view.type, view.explicitLock);
455
- if (view.capability === capability && Math.abs(view.progress - progress) < 1e-4 && view.navigation === navigation) {
456
- return state;
457
- }
458
- const newViews = [...state.views];
459
- newViews[viewIndex] = {
460
- ...view,
461
- metrics,
462
- capability,
463
- progress,
464
- navigation
465
- };
466
- return { views: newViews };
467
- });
468
- },
469
- processIntention: (intention) => {
470
- const state = get();
471
- if (state.isTransitioning || state.isGlobalLocked) return false;
472
- const activeView = state.views[state.activeIndex];
473
- if (!activeView) return false;
474
- if (intention.type === "navigate") {
475
- if (intention.direction === "down") {
476
- if (activeView.navigation === "locked") return false;
477
- if (state.activeIndex < state.totalViews - 1) {
478
- get().goToView(state.activeIndex + 1);
479
- return true;
480
- }
481
- } else if (intention.direction === "up") {
482
- const isAtTop = activeView.metrics.scrollTop <= 1;
483
- if (activeView.capability === "internal" && !isAtTop) {
484
- return false;
485
- }
486
- if (activeView.type === "controlled") {
487
- const config = activeView.config;
488
- if (config.allowGoBack === false) {
489
- return false;
490
- }
491
- } else {
492
- if (activeView.explicitLock === "locked") return false;
493
- }
494
- if (state.activeIndex > 0) {
495
- get().goToView(state.activeIndex - 1);
496
- return true;
497
- }
498
- }
499
- }
500
- return false;
501
- },
502
- goToNext: () => {
503
- const state = get();
504
- state.goToView(state.activeIndex + 1);
505
- },
506
- goToPrevious: () => {
507
- const state = get();
508
- state.goToView(state.activeIndex - 1);
509
- },
510
- goToView: (indexOrId) => {
511
- const state = get();
512
- const now = Date.now();
513
- if (now - lastNavigationTime < NAVIGATION_COOLDOWN$1) return;
514
- lastNavigationTime = now;
515
- let targetIndex = typeof indexOrId === "string" ? state.views.findIndex((v) => v.id === indexOrId) : indexOrId;
516
- if (targetIndex < 0 || targetIndex >= state.totalViews) return;
517
- if (targetIndex === state.activeIndex) return;
518
- set((s) => ({
519
- ...s,
520
- isTransitioning: true,
521
- activeIndex: targetIndex,
522
- activeId: s.views[targetIndex]?.id ?? null,
523
- views: s.views.map((v) => {
524
- if (v.index === targetIndex) return { ...v, isActive: true };
525
- if (v.index === s.activeIndex) return { ...v, isActive: false };
526
- return v;
527
- })
528
- }));
529
- },
530
- setViewExplicitLock: (id, lock) => {
531
- set((state) => {
532
- const index = state.views.findIndex((v) => v.id === id);
533
- if (index === -1) return state;
534
- const view = state.views[index];
535
- const navigation = evaluateStateMachine(view.capability, view.progress, view.type, lock);
536
- const newViews = [...state.views];
537
- newViews[index] = { ...view, explicitLock: lock, navigation };
538
- return { views: newViews };
539
- });
540
- },
541
- setGlobalLock: (locked) => set({ isGlobalLocked: locked }),
542
- setDragging: (dragging) => set({ isDragging: dragging }),
543
- getViewById: (id) => get().views.find((v) => v.id === id),
544
- startTransition: () => set({ isTransitioning: true }),
545
- endTransition: () => set({ isTransitioning: false }),
546
- resetNavigationCooldown: () => {
547
- lastNavigationTime = 0;
548
- }
549
- }))
550
- );
551
- var selectActiveView$1 = (state) => state.views[state.activeIndex];
552
- var selectActiveViewProgress$1 = (state) => state.views[state.activeIndex]?.progress ?? 0;
553
- var selectCanNavigateNext$1 = (state) => {
554
- if (state.isTransitioning || state.isGlobalLocked) return false;
555
- const activeView = state.views[state.activeIndex];
556
- if (!activeView) return false;
557
- return activeView.navigation === "unlocked";
558
- };
559
- var selectCanNavigatePrevious$1 = (state) => {
560
- if (state.isTransitioning || state.isGlobalLocked) return false;
561
- return state.activeIndex > 0;
562
- };
563
-
564
- // hooks/useWheelHandler.ts
565
- var import_react = require("react");
566
-
567
- // utils/normalizeWheel.ts
568
- function normalizeWheel(event) {
569
- let pixelX = 0;
570
- let pixelY = 0;
571
- let pixelZ = 0;
572
- if ("detail" in event) {
573
- pixelY = event.detail;
574
- }
575
- if ("wheelDelta" in event) {
576
- pixelY = -event.wheelDelta / 120;
577
- }
578
- if ("wheelDeltaY" in event) {
579
- pixelY = -event.wheelDeltaY / 120;
580
- }
581
- if ("wheelDeltaX" in event) {
582
- pixelX = -event.wheelDeltaX / 120;
583
- }
584
- if ("deltaY" in event) {
585
- pixelY = event.deltaY;
586
- }
587
- if ("deltaX" in event) {
588
- pixelX = event.deltaX;
589
- }
590
- if ((event.deltaMode || 0) === 1) {
591
- pixelY *= 40;
592
- pixelX *= 40;
593
- } else if ((event.deltaMode || 0) === 2) {
594
- pixelY *= 800;
595
- pixelX *= 800;
596
- }
597
- return { pixelX, pixelY, pixelZ };
598
- }
599
-
600
- // hooks/useWheelHandler.ts
601
- function useWheelHandler$1() {
602
- const scrollAccumulator = (0, import_react.useRef)(0);
603
- const lastScrollTime = (0, import_react.useRef)(0);
604
- useScrollStore$1((s) => s.isTransitioning);
605
- (0, import_react.useEffect)(() => {
606
- const handleWheel = (event) => {
607
- const state = useScrollStore$1.getState();
608
- if (state.isTransitioning || state.isDragging) {
609
- event.preventDefault();
610
- return;
611
- }
612
- const normalized = normalizeWheel(event);
613
- const delta = normalized.pixelY;
614
- scrollAccumulator.current += delta;
615
- const now = Date.now();
616
- if (now - lastScrollTime.current > 200) {
617
- scrollAccumulator.current = delta;
618
- }
619
- lastScrollTime.current = now;
620
- if (Math.abs(scrollAccumulator.current) >= NAV_THRESHOLDS$1.WHEEL) {
621
- const direction = scrollAccumulator.current > 0 ? "down" : "up";
622
- const intention = {
623
- type: "navigate",
624
- direction,
625
- strength: Math.min(Math.abs(scrollAccumulator.current) / NAV_THRESHOLDS$1.WHEEL, 1),
626
- origin: "wheel"
627
- };
628
- const handled = useScrollStore$1.getState().processIntention(intention);
629
- if (handled) {
630
- scrollAccumulator.current = 0;
631
- event.preventDefault();
632
- }
633
- }
634
- };
635
- window.addEventListener("wheel", handleWheel, { passive: false });
636
- return () => window.removeEventListener("wheel", handleWheel);
637
- }, []);
638
- }
639
-
640
- // hooks/useTouchHandler.ts
641
- var import_react2 = require("react");
642
- function useTouchHandler$1(options = {}) {
643
- const { enabled = true } = options;
644
- const touchStart = (0, import_react2.useRef)(null);
645
- const touchStartTime = (0, import_react2.useRef)(0);
646
- (0, import_react2.useEffect)(() => {
647
- if (!enabled) return;
648
- const handleTouchStart = (e) => {
649
- touchStart.current = {
650
- x: e.touches[0].clientX,
651
- y: e.touches[0].clientY
652
- };
653
- touchStartTime.current = Date.now();
654
- };
655
- const handleTouchEnd = (e) => {
656
- if (!touchStart.current) return;
657
- const touchEnd = {
658
- x: e.changedTouches[0].clientX,
659
- y: e.changedTouches[0].clientY
660
- };
661
- const deltaY = touchStart.current.y - touchEnd.y;
662
- const deltaX = touchStart.current.x - touchEnd.x;
663
- const timeElapsed = Date.now() - touchStartTime.current;
664
- if (Math.abs(deltaY) > Math.abs(deltaX) && // Vertical
665
- Math.abs(deltaY) > NAV_THRESHOLDS$1.TOUCH && // Threshold distancia
666
- timeElapsed < 800) {
667
- const direction = deltaY > 0 ? "down" : "up";
668
- const intention = {
669
- type: "navigate",
670
- direction,
671
- strength: 1,
672
- // Swipes son intenciones fuertes
673
- origin: "touch"
674
- };
675
- useScrollStore$1.getState().processIntention(intention);
676
- }
677
- touchStart.current = null;
678
- };
679
- window.addEventListener("touchstart", handleTouchStart, { passive: true });
680
- window.addEventListener("touchend", handleTouchEnd, { passive: true });
681
- return () => {
682
- window.removeEventListener("touchstart", handleTouchStart);
683
- window.removeEventListener("touchend", handleTouchEnd);
684
- };
685
- }, [enabled]);
686
- }
687
-
688
- // hooks/useKeyboardHandler.ts
689
- var import_react3 = require("react");
690
- function useKeyboardHandler$1(options = {}) {
691
- const { enabled = true, preventDefault = true } = options;
692
- (0, import_react3.useEffect)(() => {
693
- if (!enabled) return;
694
- const handleKeyDown = (e) => {
695
- const target = e.target;
696
- if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
697
- return;
698
- }
699
- let intention = null;
700
- switch (e.key) {
701
- case "ArrowDown":
702
- case "PageDown":
703
- intention = {
704
- type: "navigate",
705
- direction: "down",
706
- strength: 1,
707
- origin: "keyboard"
708
- };
709
- break;
710
- case "ArrowUp":
711
- case "PageUp":
712
- intention = {
713
- type: "navigate",
714
- direction: "up",
715
- strength: 1,
716
- origin: "keyboard"
717
- };
718
- break;
719
- case " ":
720
- intention = {
721
- type: "navigate",
722
- direction: e.shiftKey ? "up" : "down",
723
- strength: 1,
724
- origin: "keyboard"
725
- };
726
- break;
727
- case "Home":
728
- useScrollStore$1.getState().goToView(0);
729
- if (preventDefault) e.preventDefault();
730
- return;
731
- case "End":
732
- const totalViews = useScrollStore$1.getState().totalViews;
733
- useScrollStore$1.getState().goToView(totalViews - 1);
734
- if (preventDefault) e.preventDefault();
735
- return;
736
- default:
737
- return;
738
- }
739
- if (intention) {
740
- if (preventDefault) e.preventDefault();
741
- useScrollStore$1.getState().processIntention(intention);
742
- }
743
- };
744
- window.addEventListener("keydown", handleKeyDown);
745
- return () => {
746
- window.removeEventListener("keydown", handleKeyDown);
747
- };
748
- }, [enabled, preventDefault]);
749
- }
750
-
751
- // hooks/useHashSync.ts
752
- var import_react4 = require("react");
753
- function useHashSync$1(options = {}) {
754
- const { enabled = true, pushHistory = false, hashPrefix = "" } = options;
755
- const hasInitialized = (0, import_react4.useRef)(false);
756
- (0, import_react4.useEffect)(() => {
757
- if (!enabled) return;
758
- const unsubscribe = useScrollStore$1.subscribe(
759
- (state) => state.activeIndex,
760
- (activeIndex, prevIndex) => {
761
- if (!hasInitialized.current) return;
762
- if (activeIndex === prevIndex) return;
763
- const views = useScrollStore$1.getState().views;
764
- const activeView = views[activeIndex];
765
- if (activeView) {
766
- const hash = `#${hashPrefix}${activeView.id}`;
767
- if (pushHistory) {
768
- window.history.pushState(null, "", hash);
769
- } else {
770
- window.history.replaceState(null, "", hash);
771
- }
772
- }
773
- }
774
- );
775
- return () => unsubscribe();
776
- }, [enabled, pushHistory, hashPrefix]);
777
- (0, import_react4.useEffect)(() => {
778
- if (!enabled) return;
779
- const handlePopState = () => {
780
- const hash = window.location.hash.slice(1);
781
- if (!hash) return;
782
- const viewId = hashPrefix ? hash.replace(hashPrefix, "") : hash;
783
- const views = useScrollStore$1.getState().views;
784
- const targetIndex = views.findIndex((v) => v.id === viewId);
785
- if (targetIndex !== -1) {
786
- useScrollStore$1.getState().goToView(targetIndex);
787
- }
788
- };
789
- window.addEventListener("popstate", handlePopState);
790
- return () => window.removeEventListener("popstate", handlePopState);
791
- }, [enabled, hashPrefix]);
792
- (0, import_react4.useEffect)(() => {
793
- if (!enabled) return;
794
- const checkAndNavigate = () => {
795
- const state = useScrollStore$1.getState();
796
- if (!state.isInitialized || state.views.length === 0) {
797
- setTimeout(checkAndNavigate, 100);
798
- return;
799
- }
800
- const hash = window.location.hash.slice(1);
801
- if (!hash) {
802
- hasInitialized.current = true;
803
- return;
804
- }
805
- const viewId = hashPrefix ? hash.replace(hashPrefix, "") : hash;
806
- const targetIndex = state.views.findIndex((v) => v.id === viewId);
807
- if (targetIndex !== -1 && targetIndex !== state.activeIndex) {
808
- state.goToView(targetIndex);
809
- }
810
- hasInitialized.current = true;
811
- };
812
- checkAndNavigate();
813
- }, [enabled, hashPrefix]);
814
- }
815
-
816
- // hooks/useDragHandler.ts
817
- var import_react5 = require("react");
818
- var DRAG_THRESHOLD = 50;
819
- var VELOCITY_THRESHOLD = 0.5;
820
- var RESISTANCE_FACTOR = 0.4;
821
- function useDragHandler$1(options = {}) {
822
- const { enabled = true, onDragUpdate, onDragEnd } = options;
823
- const [dragState, setDragState] = (0, import_react5.useState)({
824
- isDragging: false,
825
- dragOffset: 0,
826
- dragDirection: null
827
- });
828
- const touchStartRef = (0, import_react5.useRef)(null);
829
- const lastMoveRef = (0, import_react5.useRef)(null);
830
- const rafRef = (0, import_react5.useRef)(null);
831
- const updateDragState = (0, import_react5.useCallback)((newState) => {
832
- setDragState((prev) => {
833
- const updated = { ...prev, ...newState };
834
- onDragUpdate?.(updated);
835
- return updated;
836
- });
837
- }, [onDragUpdate]);
838
- (0, import_react5.useEffect)(() => {
839
- if (!enabled) return;
840
- const handleTouchStart = (e) => {
841
- const target = e.target;
842
- if (target.closest('[data-scrollable="true"]')) return;
843
- touchStartRef.current = {
844
- y: e.touches[0].clientY,
845
- time: Date.now()
846
- };
847
- lastMoveRef.current = touchStartRef.current;
848
- useScrollStore$1.getState().setDragging(true);
849
- updateDragState({ isDragging: true, dragOffset: 0, dragDirection: null });
850
- };
851
- const handleTouchMove = (e) => {
852
- if (!touchStartRef.current) return;
853
- const currentY = e.touches[0].clientY;
854
- const deltaY = touchStartRef.current.y - currentY;
855
- const viewportHeight = window.innerHeight;
856
- let offset = deltaY / viewportHeight;
857
- const store = useScrollStore$1.getState();
858
- const atStart = store.activeIndex === 0 && deltaY < 0;
859
- const atEnd = store.activeIndex === store.totalViews - 1 && deltaY > 0;
860
- if (atStart || atEnd) {
861
- offset = offset * RESISTANCE_FACTOR;
862
- }
863
- offset = Math.max(-1, Math.min(1, offset));
864
- lastMoveRef.current = { y: currentY, time: Date.now() };
865
- if (rafRef.current) cancelAnimationFrame(rafRef.current);
866
- rafRef.current = requestAnimationFrame(() => {
867
- updateDragState({
868
- dragOffset: offset,
869
- dragDirection: deltaY > 0 ? "down" : deltaY < 0 ? "up" : null
870
- });
871
- });
872
- };
873
- const handleTouchEnd = (e) => {
874
- if (!touchStartRef.current || !lastMoveRef.current) {
875
- updateDragState({ isDragging: false, dragOffset: 0, dragDirection: null });
876
- return;
877
- }
878
- const endY = e.changedTouches[0].clientY;
879
- const deltaY = touchStartRef.current.y - endY;
880
- const timeDelta = Date.now() - lastMoveRef.current.time;
881
- const velocity = timeDelta > 0 ? Math.abs(deltaY) / timeDelta : 0;
882
- const store = useScrollStore$1.getState();
883
- const atStart = store.activeIndex === 0;
884
- const atEnd = store.activeIndex === store.totalViews - 1;
885
- const exceedsThreshold = Math.abs(deltaY) > DRAG_THRESHOLD;
886
- const hasVelocity = velocity > VELOCITY_THRESHOLD;
887
- const direction = deltaY > 0 ? "down" : "up";
888
- const canNavigate = direction === "down" && !atEnd || direction === "up" && !atStart;
889
- const shouldNavigate = canNavigate && (exceedsThreshold || hasVelocity);
890
- onDragEnd?.(shouldNavigate, direction);
891
- if (shouldNavigate) {
892
- store.processIntention({
893
- type: "navigate",
894
- direction,
895
- strength: Math.min(1, velocity / VELOCITY_THRESHOLD),
896
- origin: "touch"
897
- });
898
- }
899
- touchStartRef.current = null;
900
- lastMoveRef.current = null;
901
- useScrollStore$1.getState().setDragging(false);
902
- updateDragState({ isDragging: false, dragOffset: 0, dragDirection: null });
903
- };
904
- const handleTouchCancel = () => {
905
- touchStartRef.current = null;
906
- lastMoveRef.current = null;
907
- useScrollStore$1.getState().setDragging(false);
908
- updateDragState({ isDragging: false, dragOffset: 0, dragDirection: null });
909
- };
910
- window.addEventListener("touchstart", handleTouchStart, { passive: true });
911
- window.addEventListener("touchmove", handleTouchMove, { passive: true });
912
- window.addEventListener("touchend", handleTouchEnd, { passive: true });
913
- window.addEventListener("touchcancel", handleTouchCancel, { passive: true });
914
- return () => {
915
- window.removeEventListener("touchstart", handleTouchStart);
916
- window.removeEventListener("touchmove", handleTouchMove);
917
- window.removeEventListener("touchend", handleTouchEnd);
918
- window.removeEventListener("touchcancel", handleTouchCancel);
919
- if (rafRef.current) cancelAnimationFrame(rafRef.current);
920
- };
921
- }, [enabled, updateDragState, onDragEnd]);
922
- return dragState;
923
- }
924
-
925
- // hooks/useFocusManagement.ts
926
- var import_react6 = require("react");
927
- function useFocusManagement$1(options = {}) {
928
- const { enabled = true, focusDelay = 100 } = options;
929
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
930
- const activeId = useScrollStore$1((s) => s.activeId);
931
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
932
- const prevIndexRef = (0, import_react6.useRef)(activeIndex);
933
- (0, import_react6.useEffect)(() => {
934
- if (!enabled) return;
935
- if (activeIndex !== prevIndexRef.current && !isTransitioning && activeId) {
936
- const timer = setTimeout(() => {
937
- const viewElement = document.getElementById(activeId);
938
- if (viewElement) {
939
- if (!viewElement.hasAttribute("tabindex")) {
940
- viewElement.setAttribute("tabindex", "-1");
941
- }
942
- viewElement.focus({ preventScroll: true });
943
- }
944
- prevIndexRef.current = activeIndex;
945
- }, focusDelay);
946
- return () => clearTimeout(timer);
947
- }
948
- }, [activeIndex, activeId, isTransitioning, enabled, focusDelay]);
949
- }
950
-
951
- // hooks/useScrollSystem.ts
952
- var import_react7 = require("react");
953
- function useScrollSystem$1() {
954
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
955
- const globalProgress = useScrollStore$1((s) => s.globalProgress);
956
- const isGlobalLocked = useScrollStore$1((s) => s.isGlobalLocked);
957
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
958
- const isDragging = useScrollStore$1((s) => s.isDragging);
959
- const activeId = useScrollStore$1((s) => s.activeId);
960
- const totalViews = useScrollStore$1((s) => s.totalViews);
961
- const views = useScrollStore$1((s) => s.views);
962
- const {
963
- goToNext: storeNext,
964
- goToPrevious: storePrev,
965
- goToView: storeGoTo
966
- } = useScrollStore$1();
967
- const activeView = views[activeIndex];
968
- const activeViewType = activeView?.type ?? null;
969
- const activeViewProgress = activeView?.progress ?? 0;
970
- const canNavigateNext = useScrollStore$1(selectCanNavigateNext$1);
971
- const canNavigatePrevious = useScrollStore$1(selectCanNavigatePrevious$1);
972
- const goToNext = (0, import_react7.useCallback)(() => {
973
- if (canNavigateNext) {
974
- storeNext();
975
- return true;
976
- }
977
- return false;
978
- }, [canNavigateNext, storeNext]);
979
- const goToPrev = (0, import_react7.useCallback)(() => {
980
- if (canNavigatePrevious) {
981
- storePrev();
982
- return true;
983
- }
984
- return false;
985
- }, [canNavigatePrevious, storePrev]);
986
- const getCurrentIndex = (0, import_react7.useCallback)(() => activeIndex, [activeIndex]);
987
- const getProgress = (0, import_react7.useCallback)(() => globalProgress, [globalProgress]);
988
- const getActiveViewProgress = (0, import_react7.useCallback)(() => activeViewProgress, [activeViewProgress]);
989
- const isLocked = (0, import_react7.useCallback)(() => isGlobalLocked || isTransitioning || !canNavigateNext, [isGlobalLocked, isTransitioning, canNavigateNext]);
990
- return (0, import_react7.useMemo)(() => ({
991
- goToNext,
992
- goToPrev,
993
- goTo: storeGoTo,
994
- getCurrentIndex,
995
- getProgress,
996
- getActiveViewProgress,
997
- isLocked,
998
- canGoNext: canNavigateNext,
999
- canGoPrev: canNavigatePrevious,
1000
- // Extended Data for Nav/UI
1001
- activeIndex,
1002
- activeId,
1003
- activeViewType,
1004
- totalViews,
1005
- // State Flags (for advanced use)
1006
- isDragging,
1007
- isTransitioning
1008
- }), [
1009
- goToNext,
1010
- goToPrev,
1011
- storeGoTo,
1012
- getCurrentIndex,
1013
- getProgress,
1014
- getActiveViewProgress,
1015
- isLocked,
1016
- canNavigateNext,
1017
- canNavigatePrevious,
1018
- activeIndex,
1019
- activeId,
1020
- activeViewType,
1021
- totalViews,
1022
- isDragging,
1023
- isTransitioning
1024
- ]);
1025
- }
1026
-
1027
- // utils/index.ts
1028
- function throttle(fn, limit) {
1029
- let lastCall = 0;
1030
- let timeoutId = null;
1031
- return ((...args) => {
1032
- const now = Date.now();
1033
- const remaining = limit - (now - lastCall);
1034
- if (remaining <= 0) {
1035
- if (timeoutId) {
1036
- clearTimeout(timeoutId);
1037
- timeoutId = null;
1038
- }
1039
- lastCall = now;
1040
- fn(...args);
1041
- } else if (!timeoutId) {
1042
- timeoutId = setTimeout(() => {
1043
- lastCall = Date.now();
1044
- timeoutId = null;
1045
- fn(...args);
1046
- }, remaining);
1047
- }
1048
- });
1049
- }
1050
- function prefersReducedMotion() {
1051
- if (typeof window === "undefined") return false;
1052
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1053
- }
1054
-
1055
- // components/ScrollContainer.tsx
1056
- var import_jsx_runtime = require("react/jsx-runtime");
1057
- function ScrollContainer({
1058
- children,
1059
- className = "",
1060
- transitionDuration = DEFAULT_TRANSITION_DURATION$1,
1061
- transitionEasing = DEFAULT_TRANSITION_EASING$1,
1062
- onViewChange,
1063
- onInitialized,
1064
- // Deep Linking options
1065
- enableHashSync = false,
1066
- hashPushHistory = false,
1067
- hashPrefix = "",
1068
- // Accessibility
1069
- respectReducedMotion = true,
1070
- enableFocusManagement = true,
1071
- // Touch Physics
1072
- enableDragPhysics = false,
1073
- // Layout
1074
- orientation = "vertical"
1075
- }) {
1076
- const containerRef = (0, import_react8.useRef)(null);
1077
- const isBrowser = typeof window !== "undefined";
1078
- const [reducedMotion, setReducedMotion] = (0, import_react8.useState)(false);
1079
- (0, import_react8.useEffect)(() => {
1080
- if (!isBrowser || !respectReducedMotion) return;
1081
- setReducedMotion(prefersReducedMotion());
1082
- const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
1083
- const handler = (e) => setReducedMotion(e.matches);
1084
- mediaQuery.addEventListener("change", handler);
1085
- return () => mediaQuery.removeEventListener("change", handler);
1086
- }, [respectReducedMotion, isBrowser]);
1087
- const effectiveDuration = reducedMotion ? 0 : transitionDuration;
1088
- const { initialize, endTransition } = useScrollStore$1();
1089
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
1090
- useScrollStore$1((s) => s.totalViews);
1091
- useScrollStore$1((s) => s.isInitialized);
1092
- const prevIndexRef = (0, import_react8.useRef)(activeIndex);
1093
- useWheelHandler$1();
1094
- useTouchHandler$1({ enabled: !enableDragPhysics });
1095
- useKeyboardHandler$1();
1096
- const dragState = useDragHandler$1({
1097
- enabled: enableDragPhysics && !reducedMotion
1098
- });
1099
- useHashSync$1({
1100
- enabled: enableHashSync,
1101
- pushHistory: hashPushHistory,
1102
- hashPrefix
1103
- });
1104
- useFocusManagement$1({ enabled: enableFocusManagement });
1105
- (0, import_react8.useEffect)(() => {
1106
- const timer = setTimeout(() => {
1107
- initialize();
1108
- onInitialized?.();
1109
- }, 50);
1110
- return () => clearTimeout(timer);
1111
- }, [initialize, onInitialized]);
1112
- (0, import_react8.useEffect)(() => {
1113
- if (prevIndexRef.current !== activeIndex) {
1114
- onViewChange?.(prevIndexRef.current, activeIndex);
1115
- const timer = setTimeout(() => {
1116
- endTransition();
1117
- }, effectiveDuration);
1118
- prevIndexRef.current = activeIndex;
1119
- return () => clearTimeout(timer);
1120
- }
1121
- }, [activeIndex, effectiveDuration, onViewChange, endTransition]);
1122
- const wrapperStyle = (0, import_react8.useMemo)(() => {
1123
- const baseOffset = activeIndex * 100;
1124
- const dragOffset = dragState.isDragging ? dragState.dragOffset * 100 : 0;
1125
- const transformAxis = orientation === "horizontal" ? "X" : "Y";
1126
- const sizeUnit = orientation === "horizontal" ? "vw" : "vh";
1127
- return {
1128
- transform: `translate${transformAxis}(-${baseOffset + dragOffset}${sizeUnit})`,
1129
- transition: dragState.isDragging ? "none" : effectiveDuration > 0 ? `transform ${effectiveDuration}ms ${transitionEasing}` : "none",
1130
- height: "100%",
1131
- width: "100%",
1132
- display: orientation === "horizontal" ? "flex" : "block",
1133
- flexDirection: orientation === "horizontal" ? "row" : void 0
1134
- };
1135
- }, [activeIndex, effectiveDuration, transitionEasing, dragState, orientation]);
1136
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1137
- "div",
1138
- {
1139
- ref: containerRef,
1140
- className: `scroll-container fixed inset-0 overflow-hidden w-screen h-screen ${className}`,
1141
- role: "main",
1142
- "aria-label": "Scroll container",
1143
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "scroll-wrapper", style: wrapperStyle, children })
1144
- }
1145
- );
1146
- }
1147
-
1148
- // components/FullView.tsx
1149
- var import_react10 = require("react");
1150
-
1151
- // hooks/useViewRegistration.ts
1152
- var import_react9 = require("react");
1153
- function useViewRegistration$1({
1154
- config,
1155
- onActivate,
1156
- onDeactivate,
1157
- onEnterStart,
1158
- onEnterEnd,
1159
- onExitStart,
1160
- onExitEnd
1161
- }) {
1162
- const registerView = useScrollStore$1((s) => s.registerView);
1163
- const unregisterView = useScrollStore$1((s) => s.unregisterView);
1164
- const activeId = useScrollStore$1((s) => s.activeId);
1165
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
1166
- const callbacksRef = (0, import_react9.useRef)({
1167
- onActivate,
1168
- onDeactivate,
1169
- onEnterStart,
1170
- onEnterEnd,
1171
- onExitStart,
1172
- onExitEnd
1173
- });
1174
- callbacksRef.current = {
1175
- onActivate,
1176
- onDeactivate,
1177
- onEnterStart,
1178
- onEnterEnd,
1179
- onExitStart,
1180
- onExitEnd
1181
- };
1182
- const wasActiveRef = (0, import_react9.useRef)(false);
1183
- const wasTransitioningRef = (0, import_react9.useRef)(false);
1184
- const configRef = (0, import_react9.useRef)(config);
1185
- if (JSON.stringify(config) !== JSON.stringify(configRef.current)) {
1186
- configRef.current = config;
1187
- }
1188
- (0, import_react9.useEffect)(() => {
1189
- const currentConfig = configRef.current;
1190
- registerView(currentConfig);
1191
- return () => unregisterView(currentConfig.id);
1192
- }, [registerView, unregisterView, configRef.current]);
1193
- const viewState = useScrollStore$1((s) => s.views.find((v) => v.id === config.id));
1194
- const isActive = activeId === config.id;
1195
- (0, import_react9.useEffect)(() => {
1196
- if (isActive && !wasActiveRef.current) {
1197
- callbacksRef.current.onActivate?.();
1198
- } else if (!isActive && wasActiveRef.current) {
1199
- callbacksRef.current.onDeactivate?.();
1200
- }
1201
- wasActiveRef.current = isActive;
1202
- }, [isActive]);
1203
- (0, import_react9.useEffect)(() => {
1204
- const callbacks = callbacksRef.current;
1205
- const wasActive = wasActiveRef.current;
1206
- const wasTransitioning = wasTransitioningRef.current;
1207
- if (isTransitioning && !wasTransitioning) {
1208
- if (isActive && !wasActive) {
1209
- callbacks.onEnterStart?.();
1210
- } else if (!isActive && wasActive) {
1211
- callbacks.onExitStart?.();
1212
- }
1213
- }
1214
- if (!isTransitioning && wasTransitioning) {
1215
- if (isActive) {
1216
- callbacks.onEnterEnd?.();
1217
- } else if (wasActive && !isActive) {
1218
- callbacks.onExitEnd?.();
1219
- }
1220
- }
1221
- wasTransitioningRef.current = isTransitioning;
1222
- }, [isTransitioning, isActive]);
1223
- return {
1224
- isActive,
1225
- viewState,
1226
- index: viewState?.index ?? -1,
1227
- scrollProgress: viewState?.progress ?? 0,
1228
- navigation: viewState?.navigation ?? "unlocked"
1229
- };
1230
- }
1231
-
1232
- // components/FullView.tsx
1233
- var import_jsx_runtime2 = require("react/jsx-runtime");
1234
- function FullView({
1235
- id,
1236
- children,
1237
- className = "",
1238
- meta,
1239
- onActivate,
1240
- onDeactivate,
1241
- onEnterStart,
1242
- onEnterEnd,
1243
- onExitStart,
1244
- onExitEnd
1245
- }) {
1246
- const config = (0, import_react10.useMemo)(
1247
- () => ({
1248
- id,
1249
- type: "full",
1250
- meta
1251
- }),
1252
- [id, meta]
1253
- );
1254
- const { isActive, index } = useViewRegistration$1({
1255
- config,
1256
- onActivate,
1257
- onDeactivate,
1258
- onEnterStart,
1259
- onEnterEnd,
1260
- onExitStart,
1261
- onExitEnd
1262
- });
1263
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1264
- "section",
1265
- {
1266
- id,
1267
- className: `
1268
- full-view
1269
- relative
1270
- w-full h-screen
1271
- overflow-hidden
1272
- ${isActive ? "is-active" : ""}
1273
- ${className}
1274
- `,
1275
- "data-view-type": "full",
1276
- "data-view-index": index,
1277
- "data-view-active": isActive,
1278
- "aria-hidden": !isActive,
1279
- children
1280
- }
1281
- );
1282
- }
1283
-
1284
- // hooks/useMetricsReporter.ts
1285
- var import_react11 = require("react");
1286
- var SCROLL_THROTTLE_MS = 66;
1287
- function useMetricsReporter$1({
1288
- id,
1289
- isActive,
1290
- scrollDirection,
1291
- onScrollProgress,
1292
- throttleMs = SCROLL_THROTTLE_MS
1293
- }) {
1294
- const scrollRef = (0, import_react11.useRef)(null);
1295
- const updateMetrics = useScrollStore$1((s) => s.updateViewMetrics);
1296
- const measureAndReport = (0, import_react11.useCallback)(() => {
1297
- const el = scrollRef.current;
1298
- if (!el) return;
1299
- const metrics = {
1300
- scrollHeight: scrollDirection === "vertical" ? el.scrollHeight : el.scrollWidth,
1301
- clientHeight: scrollDirection === "vertical" ? el.clientHeight : el.clientWidth,
1302
- scrollTop: scrollDirection === "vertical" ? el.scrollTop : el.scrollLeft
1303
- };
1304
- updateMetrics(id, metrics);
1305
- if (onScrollProgress) {
1306
- const max = metrics.scrollHeight - metrics.clientHeight;
1307
- const progress = max > 0 ? metrics.scrollTop / max : 1;
1308
- onScrollProgress(progress);
1309
- }
1310
- }, [id, scrollDirection, updateMetrics, onScrollProgress]);
1311
- const throttledMeasure = (0, import_react11.useMemo)(
1312
- () => throttle(measureAndReport, throttleMs),
1313
- [measureAndReport, throttleMs]
1314
- );
1315
- (0, import_react11.useEffect)(() => {
1316
- const el = scrollRef.current;
1317
- if (!el) return;
1318
- const resizeObserver = new ResizeObserver(() => {
1319
- window.requestAnimationFrame(measureAndReport);
1320
- });
1321
- resizeObserver.observe(el);
1322
- Array.from(el.children).forEach((child) => resizeObserver.observe(child));
1323
- el.addEventListener("scroll", throttledMeasure, { passive: true });
1324
- measureAndReport();
1325
- return () => {
1326
- resizeObserver.disconnect();
1327
- el.removeEventListener("scroll", throttledMeasure);
1328
- };
1329
- }, [measureAndReport, throttledMeasure]);
1330
- (0, import_react11.useEffect)(() => {
1331
- if (isActive) {
1332
- measureAndReport();
1333
- }
1334
- }, [isActive, measureAndReport]);
1335
- return { scrollRef, measureAndReport };
1336
- }
1337
-
1338
- // components/ScrollLockedView.tsx
1339
- var import_jsx_runtime3 = require("react/jsx-runtime");
1340
- function ScrollLockedView({
1341
- id,
1342
- children,
1343
- className = "",
1344
- scrollDirection = "vertical",
1345
- scrollEndThreshold = 0.99,
1346
- onScrollProgress,
1347
- onActivate,
1348
- onDeactivate,
1349
- onEnterStart,
1350
- onEnterEnd,
1351
- onExitStart,
1352
- onExitEnd
1353
- }) {
1354
- const { isActive} = useViewRegistration$1({
1355
- config: {
1356
- id,
1357
- type: "scroll-locked",
1358
- scrollDirection,
1359
- scrollEndThreshold
1360
- },
1361
- onActivate,
1362
- onDeactivate,
1363
- onEnterStart,
1364
- onEnterEnd,
1365
- onExitStart,
1366
- onExitEnd
1367
- });
1368
- const { scrollRef } = useMetricsReporter$1({
1369
- id,
1370
- isActive,
1371
- scrollDirection,
1372
- onScrollProgress
1373
- });
1374
- const scrollClasses = scrollDirection === "vertical" ? "overflow-y-auto overflow-x-hidden" : "overflow-x-auto overflow-y-hidden";
1375
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1376
- "section",
1377
- {
1378
- id,
1379
- className: `relative w-full h-screen ${className}`,
1380
- "data-view-type": "scroll-locked",
1381
- "data-active": isActive,
1382
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1383
- "div",
1384
- {
1385
- ref: scrollRef,
1386
- className: `w-full h-full ${scrollClasses} no-scrollbar`,
1387
- style: { scrollbarWidth: "none" },
1388
- children
1389
- }
1390
- )
1391
- }
1392
- );
1393
- }
1394
-
1395
- // components/ControlledView.tsx
1396
- var import_react12 = require("react");
1397
- var import_jsx_runtime4 = require("react/jsx-runtime");
1398
- function ControlledView({
1399
- id,
1400
- children,
1401
- className = "",
1402
- scrollDirection = "none",
1403
- allowInternalScroll = false,
1404
- canProceed = false,
1405
- allowGoBack = true,
1406
- onActivate,
1407
- onDeactivate,
1408
- onEnterStart,
1409
- onEnterEnd,
1410
- onExitStart,
1411
- onExitEnd
1412
- }) {
1413
- const setExplicitLock = useScrollStore$1((s) => s.setViewExplicitLock);
1414
- const config = (0, import_react12.useMemo)(
1415
- () => ({
1416
- id,
1417
- type: "controlled",
1418
- scrollDirection,
1419
- allowInternalScroll,
1420
- allowGoBack
1421
- }),
1422
- [id, scrollDirection, allowInternalScroll, allowGoBack]
1423
- );
1424
- const { isActive} = useViewRegistration$1({
1425
- config,
1426
- onActivate,
1427
- onDeactivate,
1428
- onEnterStart,
1429
- onEnterEnd,
1430
- onExitStart,
1431
- onExitEnd
1432
- });
1433
- const { scrollRef } = useMetricsReporter$1({
1434
- id,
1435
- isActive,
1436
- scrollDirection: allowInternalScroll ? scrollDirection : "none"
1437
- });
1438
- (0, import_react12.useEffect)(() => {
1439
- const lockState = canProceed ? "unlocked" : "locked";
1440
- setExplicitLock(id, lockState);
1441
- }, [id, canProceed, setExplicitLock]);
1442
- const overflowClasses = (0, import_react12.useMemo)(() => {
1443
- if (!allowInternalScroll) return "overflow-hidden";
1444
- switch (scrollDirection) {
1445
- case "vertical":
1446
- return "overflow-y-auto overflow-x-hidden";
1447
- case "horizontal":
1448
- return "overflow-x-auto overflow-y-hidden";
1449
- default:
1450
- return "overflow-auto";
1451
- }
1452
- }, [allowInternalScroll, scrollDirection]);
1453
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1454
- "section",
1455
- {
1456
- id,
1457
- className: `relative w-full h-screen ${isActive ? "z-10" : "z-0"} ${className}`,
1458
- "data-view-type": "controlled",
1459
- "data-active": isActive,
1460
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1461
- "div",
1462
- {
1463
- ref: scrollRef,
1464
- className: `w-full h-full ${overflowClasses}`,
1465
- children
1466
- }
1467
- )
1468
- }
1469
- );
1470
- }
1471
- function useViewControl(viewId) {
1472
- const setExplicitLock = useScrollStore$1((s) => s.setViewExplicitLock);
1473
- const goToNext = useScrollStore$1((s) => s.goToNext);
1474
- const goToPrevious = useScrollStore$1((s) => s.goToPrevious);
1475
- const goToView = useScrollStore$1((s) => s.goToView);
1476
- return (0, import_react12.useMemo)(
1477
- () => ({
1478
- unlock: () => setExplicitLock(viewId, "unlocked"),
1479
- lock: () => setExplicitLock(viewId, "locked"),
1480
- // Navegación Programática (Forzada)
1481
- goNext: () => goToNext(),
1482
- goPrev: () => goToPrevious(),
1483
- goTo: (to) => goToView(to)
1484
- }),
1485
- [viewId, setExplicitLock, goToNext, goToPrevious, goToView]
1486
- );
1487
- }
1488
-
1489
- // components/ScrollDebugOverlay.tsx
1490
- var import_jsx_runtime5 = require("react/jsx-runtime");
1491
- function ScrollDebugOverlay({
1492
- position = "bottom-left",
1493
- visible = true
1494
- }) {
1495
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
1496
- const totalViews = useScrollStore$1((s) => s.totalViews);
1497
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
1498
- const isGlobalLocked = useScrollStore$1((s) => s.isGlobalLocked);
1499
- const isInitialized = useScrollStore$1((s) => s.isInitialized);
1500
- const views = useScrollStore$1((s) => s.views);
1501
- const activeView = views[activeIndex];
1502
- if (!visible) return null;
1503
- const positionClasses = {
1504
- "top-left": "top-4 left-4",
1505
- "top-right": "top-4 right-4",
1506
- "bottom-left": "bottom-4 left-4",
1507
- "bottom-right": "bottom-4 right-4"
1508
- };
1509
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1510
- "div",
1511
- {
1512
- 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`,
1513
- style: { pointerEvents: "none" },
1514
- children: [
1515
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "text-green-300 font-bold mb-2 flex items-center gap-2", children: [
1516
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-2 h-2 rounded-full bg-green-500 animate-pulse" }),
1517
- "ScrollSystem Debug"
1518
- ] }),
1519
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1 mb-3", children: [
1520
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "initialized", value: isInitialized }),
1521
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "activeIndex", value: `${activeIndex} / ${totalViews - 1}` }),
1522
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "transitioning", value: isTransitioning }),
1523
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "globalLocked", value: isGlobalLocked })
1524
- ] }),
1525
- activeView && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
1526
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-green-300/70 mb-1", children: "Active View" }),
1527
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "id", value: activeView.id }),
1528
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "type", value: activeView.type }),
1529
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "capability", value: activeView.capability }),
1530
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "navigation", value: activeView.navigation }),
1531
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "progress", value: `${(activeView.progress * 100).toFixed(0)}%` }),
1532
- activeView.explicitLock && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "explicitLock", value: activeView.explicitLock, highlight: true })
1533
- ] }),
1534
- activeView?.metrics && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border-t border-green-500/20 pt-2 mt-2", children: [
1535
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-green-300/70 mb-1", children: "Metrics" }),
1536
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "scrollHeight", value: activeView.metrics.scrollHeight }),
1537
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "clientHeight", value: activeView.metrics.clientHeight }),
1538
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Row, { label: "scrollTop", value: Math.round(activeView.metrics.scrollTop) })
1539
- ] })
1540
- ]
1541
- }
1542
- );
1543
- }
1544
- function Row({
1545
- label,
1546
- value,
1547
- highlight = false
1548
- }) {
1549
- const displayValue = typeof value === "boolean" ? value ? "\u2713" : "\u2717" : String(value);
1550
- const valueColor = typeof value === "boolean" ? value ? "text-green-400" : "text-red-400" : highlight ? "text-yellow-400" : "text-white";
1551
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-between gap-4", children: [
1552
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-green-500/70", children: [
1553
- label,
1554
- ":"
1555
- ] }),
1556
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: valueColor, children: displayValue })
1557
- ] });
1558
- }
1559
-
1560
- // components/AriaLiveRegion.tsx
1561
- var import_react13 = require("react");
1562
- var import_jsx_runtime6 = require("react/jsx-runtime");
1563
- function AriaLiveRegion({
1564
- template = "Navigated to section {viewIndex} of {totalViews}",
1565
- politeness = "polite"
1566
- }) {
1567
- const [announcement, setAnnouncement] = (0, import_react13.useState)("");
1568
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
1569
- const activeId = useScrollStore$1((s) => s.activeId);
1570
- const totalViews = useScrollStore$1((s) => s.totalViews);
1571
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
1572
- (0, import_react13.useEffect)(() => {
1573
- if (!isTransitioning && activeId) {
1574
- const message = template.replace("{viewIndex}", String(activeIndex + 1)).replace("{viewId}", activeId).replace("{totalViews}", String(totalViews));
1575
- const timer = setTimeout(() => {
1576
- setAnnouncement(message);
1577
- }, 100);
1578
- return () => clearTimeout(timer);
1579
- }
1580
- }, [activeIndex, activeId, totalViews, isTransitioning, template]);
1581
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1582
- "div",
1583
- {
1584
- role: "status",
1585
- "aria-live": politeness,
1586
- "aria-atomic": "true",
1587
- className: "sr-only",
1588
- style: {
1589
- position: "absolute",
1590
- width: "1px",
1591
- height: "1px",
1592
- padding: 0,
1593
- margin: "-1px",
1594
- overflow: "hidden",
1595
- clip: "rect(0, 0, 0, 0)",
1596
- whiteSpace: "nowrap",
1597
- border: 0
1598
- },
1599
- children: announcement
1600
- }
1601
- );
1602
- }
1603
-
1604
- // components/LazyView.tsx
1605
- var import_react14 = require("react");
1606
- var import_jsx_runtime7 = require("react/jsx-runtime");
1607
- function LazyView({
1608
- viewId,
1609
- buffer = 1,
1610
- children,
1611
- placeholder = null
1612
- }) {
1613
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
1614
- const views = useScrollStore$1((s) => s.views);
1615
- const shouldRender = (0, import_react14.useMemo)(() => {
1616
- const viewIndex = views.findIndex((v) => v.id === viewId);
1617
- if (viewIndex === -1) return false;
1618
- const minIndex = Math.max(0, activeIndex - buffer);
1619
- const maxIndex = Math.min(views.length - 1, activeIndex + buffer);
1620
- return viewIndex >= minIndex && viewIndex <= maxIndex;
1621
- }, [viewId, views, activeIndex, buffer]);
1622
- if (!shouldRender) {
1623
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: placeholder });
1624
- }
1625
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
1626
- }
1627
-
1628
- // hooks/useNavigation.ts
1629
- var import_react15 = require("react");
1630
- function useNavigation() {
1631
- const goToView = useScrollStore$1((s) => s.goToView);
1632
- const goToNextAction = useScrollStore$1((s) => s.goToNext);
1633
- const goToPreviousAction = useScrollStore$1((s) => s.goToPrevious);
1634
- const setGlobalLock = useScrollStore$1((s) => s.setGlobalLock);
1635
- const lockScroll = (0, import_react15.useCallback)(() => setGlobalLock(true), [setGlobalLock]);
1636
- const unlockScroll = (0, import_react15.useCallback)(() => setGlobalLock(false), [setGlobalLock]);
1637
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
1638
- const activeId = useScrollStore$1((s) => s.activeId);
1639
- const totalViews = useScrollStore$1((s) => s.totalViews);
1640
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
1641
- const isScrollLocked = useScrollStore$1((s) => s.isGlobalLocked);
1642
- const canNavigateNext = useScrollStore$1(selectCanNavigateNext$1);
1643
- const canNavigatePrevious = useScrollStore$1(selectCanNavigatePrevious$1);
1644
- const goToNext = (0, import_react15.useCallback)(() => {
1645
- return goToNextAction();
1646
- }, [goToNextAction]);
1647
- const goToPrevious = (0, import_react15.useCallback)(() => {
1648
- return goToPreviousAction();
1649
- }, [goToPreviousAction]);
1650
- const isFirstView = activeIndex === 0;
1651
- const isLastView = activeIndex === totalViews - 1;
1652
- const progress = totalViews > 1 ? activeIndex / (totalViews - 1) : 0;
1653
- return (0, import_react15.useMemo)(
1654
- () => ({
1655
- // Acciones de navegación
1656
- goToView,
1657
- goToNext,
1658
- goToPrevious,
1659
- // Control de bloqueo
1660
- lockScroll,
1661
- unlockScroll,
1662
- // Estado actual
1663
- activeIndex,
1664
- activeId,
1665
- totalViews,
1666
- // Estados de UI
1667
- isTransitioning,
1668
- isScrollLocked,
1669
- canNavigateNext,
1670
- canNavigatePrevious,
1671
- // Utilidades
1672
- isFirstView,
1673
- isLastView,
1674
- progress
1675
- }),
1676
- [
1677
- goToView,
1678
- goToNext,
1679
- goToPrevious,
1680
- lockScroll,
1681
- unlockScroll,
1682
- activeIndex,
1683
- activeId,
1684
- totalViews,
1685
- isTransitioning,
1686
- isScrollLocked,
1687
- canNavigateNext,
1688
- canNavigatePrevious,
1689
- isFirstView,
1690
- isLastView,
1691
- progress
1692
- ]
1693
- );
1694
- }
1695
-
1696
- // hooks/useViewProgress.ts
1697
- function useViewProgress$1(viewId) {
1698
- const progress = useScrollStore$1(
1699
- (s) => s.views.find((v) => v.id === viewId)?.progress ?? 0
1700
- );
1701
- const navigation = useScrollStore$1(
1702
- (s) => s.views.find((v) => v.id === viewId)?.navigation ?? "unlocked"
1703
- );
1704
- const isAtStart = progress <= 0.02;
1705
- const isAtEnd = progress >= 0.99;
1706
- return {
1707
- progress,
1708
- isAtStart,
1709
- isAtEnd,
1710
- navigation
1711
- };
1712
- }
1713
- function useActiveViewProgress$1() {
1714
- const progress = useScrollStore$1(selectActiveViewProgress$1);
1715
- const activeView = useScrollStore$1((s) => s.views[s.activeIndex]);
1716
- const hasInternalScroll = activeView?.capability === "internal";
1717
- return {
1718
- progress,
1719
- hasInternalScroll,
1720
- viewType: activeView?.type
1721
- };
1722
- }
1723
-
1724
- // hooks/useScrollAnalytics.ts
1725
- var import_react16 = require("react");
1726
- function useScrollAnalytics$1(options = {}) {
1727
- const { onViewEnter, onViewExit, enabled = true } = options;
1728
- const activeIndex = useScrollStore$1((s) => s.activeIndex);
1729
- const activeId = useScrollStore$1((s) => s.activeId);
1730
- const isTransitioning = useScrollStore$1((s) => s.isTransitioning);
1731
- const enterTimeRef = (0, import_react16.useRef)(Date.now());
1732
- const prevIndexRef = (0, import_react16.useRef)(activeIndex);
1733
- const prevIdRef = (0, import_react16.useRef)(activeId);
1734
- const createAnalytics = (0, import_react16.useCallback)((viewId, viewIndex, enterTime, isActive, exitTime = null) => ({
1735
- viewId,
1736
- viewIndex,
1737
- enterTime,
1738
- exitTime,
1739
- duration: exitTime ? (exitTime - enterTime) / 1e3 : 0,
1740
- isActive
1741
- }), []);
1742
- (0, import_react16.useEffect)(() => {
1743
- if (!enabled) return;
1744
- if (!isTransitioning && (activeIndex !== prevIndexRef.current || activeId !== prevIdRef.current)) {
1745
- const now = Date.now();
1746
- if (prevIdRef.current) {
1747
- const exitAnalytics = createAnalytics(
1748
- prevIdRef.current,
1749
- prevIndexRef.current,
1750
- enterTimeRef.current,
1751
- false,
1752
- now
1753
- );
1754
- onViewExit?.(exitAnalytics);
1755
- }
1756
- if (activeId) {
1757
- enterTimeRef.current = now;
1758
- const enterAnalytics = createAnalytics(
1759
- activeId,
1760
- activeIndex,
1761
- now,
1762
- true
1763
- );
1764
- onViewEnter?.(enterAnalytics);
1765
- }
1766
- prevIndexRef.current = activeIndex;
1767
- prevIdRef.current = activeId;
1768
- }
1769
- }, [activeIndex, activeId, isTransitioning, enabled, onViewEnter, onViewExit, createAnalytics]);
1770
- return {
1771
- currentViewId: activeId,
1772
- currentViewIndex: activeIndex,
1773
- viewStartTime: enterTimeRef.current,
1774
- getTimeInView: () => (Date.now() - enterTimeRef.current) / 1e3
1775
- };
1776
- }
1777
-
1778
- interface UseViewProgressResult {
1779
- progress: number;
1780
- isAtStart: boolean;
1781
- isAtEnd: boolean;
1782
- navigation: NavigationState;
1783
- }
1784
- declare function useViewProgress(viewId: string): UseViewProgressResult;
1785
- declare function useActiveViewProgress(): {
1786
- progress: number;
1787
- hasInternalScroll: boolean;
1788
- viewType: undefined;
1789
- };
1790
-
1791
- /**
1792
- * PABRIX Scroll System - Wheel Handler
1793
- * =====================================
1794
- * Determina la INTENCIÓN del usuario basada en eventos de Wheel.
1795
- * Traduce deltaY -> Intention -> Store.processIntention()
1796
- */
1797
- declare function useWheelHandler(): void;
1798
-
1799
- /**
1800
- * Scroll System - Touch Handler
1801
- * =====================================
1802
- * Determina la INTENCIÓN del usuario basada en Gestos Táctiles (Swipe).
1803
- * Traduce Touch Events -> Intention -> Store.processIntention()
1804
- */
1805
- interface UseTouchHandlerOptions {
1806
- /** Enable/disable the touch handler (default: true) */
1807
- enabled?: boolean;
1808
- }
1809
- declare function useTouchHandler(options?: UseTouchHandlerOptions): void;
1810
-
1811
- /**
1812
- * Scroll System - Keyboard Handler
1813
- * =========================================
1814
- * Captures keyboard input and translates it to navigation intentions.
1815
- *
1816
- * Supported Keys:
1817
- * - ArrowUp / ArrowDown: Navigate between views
1818
- * - PageUp / PageDown: Navigate between views
1819
- * - Space: Navigate to next view (Shift+Space for previous)
1820
- * - Home / End: Go to first / last view
1821
- */
1822
- interface UseKeyboardHandlerOptions {
1823
- /** Enable/disable keyboard navigation (default: true) */
1824
- enabled?: boolean;
1825
- /** Prevent default behavior for handled keys (default: true) */
1826
- preventDefault?: boolean;
1827
- }
1828
- declare function useKeyboardHandler(options?: UseKeyboardHandlerOptions): void;
1829
-
1830
- /**
1831
- * Scroll System - Hash Sync Handler
1832
- * ==========================================
1833
- * Synchronizes active view with URL hash for deep linking.
1834
- *
1835
- * Features:
1836
- * - Updates URL hash when view changes (pushState or replaceState)
1837
- * - Navigates to view when URL hash changes (popstate)
1838
- * - Handles initial load navigation based on hash
1839
- *
1840
- * Usage:
1841
- * useHashSync({ enabled: true, pushHistory: true })
1842
- */
1843
- interface UseHashSyncOptions {
1844
- /** Enable/disable hash syncing (default: true) */
1845
- enabled?: boolean;
1846
- /** Use pushState (true) or replaceState (false) for hash updates (default: false) */
1847
- pushHistory?: boolean;
1848
- /** Prefix for hash (e.g., "view-" creates "#view-0") (default: "") */
1849
- hashPrefix?: string;
1850
- }
1851
- declare function useHashSync(options?: UseHashSyncOptions): void;
1852
-
1853
- /**
1854
- * Scroll System - Drag Handler (Touch Physics)
1855
- * ==============================================
1856
- * Implements 1:1 direct manipulation for touch devices.
1857
- *
1858
- * Features:
1859
- * - Real-time tracking of finger position
1860
- * - Outputs dragOffset for visual feedback
1861
- * - Spring-based release detection (snap vs. proceed)
1862
- *
1863
- * This hook works alongside useTouchHandler which handles discrete swipes.
1864
- */
1865
- interface DragState {
1866
- isDragging: boolean;
1867
- dragOffset: number;
1868
- dragDirection: "up" | "down" | null;
1869
- }
1870
- interface UseDragHandlerOptions {
1871
- /** Enable/disable drag handling (default: true) */
1872
- enabled?: boolean;
1873
- /** Callback when drag state changes */
1874
- onDragUpdate?: (state: DragState) => void;
1875
- /** Callback when drag completes with navigation decision */
1876
- onDragEnd?: (shouldNavigate: boolean, direction: "up" | "down") => void;
1877
- }
1878
- declare function useDragHandler(options?: UseDragHandlerOptions): DragState;
1879
-
1880
- /**
1881
- * Scroll System - Focus Management Hook
1882
- * ======================================
1883
- * Manages focus when navigating between views.
1884
- * Moves focus to the active view section for screen reader accessibility.
1885
- */
1886
- interface UseFocusManagementOptions {
1887
- /** Enable/disable focus management (default: true) */
1888
- enabled?: boolean;
1889
- /** Delay before moving focus (after transition) */
1890
- focusDelay?: number;
1891
- }
1892
- /**
1893
- * Hook that manages keyboard focus when views change.
1894
- * Should be called once in ScrollContainer.
1895
- */
1896
- declare function useFocusManagement(options?: UseFocusManagementOptions): void;
1897
-
1898
- /**
1899
- * Scroll System - Analytics Hook
1900
- * ================================
1901
- * Tracks view engagement metrics for analytics.
1902
- */
1903
- interface ViewAnalytics {
1904
- viewId: string;
1905
- viewIndex: number;
1906
- enterTime: number;
1907
- exitTime: number | null;
1908
- duration: number;
1909
- isActive: boolean;
1910
- }
1911
- interface UseScrollAnalyticsOptions {
1912
- /** Callback when user enters a view */
1913
- onViewEnter?: (analytics: ViewAnalytics) => void;
1914
- /** Callback when user exits a view */
1915
- onViewExit?: (analytics: ViewAnalytics) => void;
1916
- /** Enable/disable tracking (default: true) */
1917
- enabled?: boolean;
1918
- }
1919
- /**
1920
- * Hook for tracking view engagement analytics.
1921
- *
1922
- * @example
1923
- * ```tsx
1924
- * useScrollAnalytics({
1925
- * onViewEnter: (data) => analytics.track("view_enter", data),
1926
- * onViewExit: (data) => analytics.track("view_exit", { ...data, duration: data.duration }),
1927
- * });
1928
- * ```
1929
- */
1930
- declare function useScrollAnalytics(options?: UseScrollAnalyticsOptions): {
1931
- currentViewId: string | null;
1932
- currentViewIndex: number;
1933
- viewStartTime: number;
1934
- getTimeInView: () => number;
1935
- };
1936
-
1937
- interface UseViewRegistrationOptions {
1938
- config: ViewConfig;
1939
- onActivate?: () => void;
1940
- onDeactivate?: () => void;
1941
- onEnterStart?: () => void;
1942
- onEnterEnd?: () => void;
1943
- onExitStart?: () => void;
1944
- onExitEnd?: () => void;
1945
- }
1946
- declare function useViewRegistration({ config, onActivate, onDeactivate, onEnterStart, onEnterEnd, onExitStart, onExitEnd, }: UseViewRegistrationOptions): {
1947
- isActive: boolean;
1948
- viewState: undefined | undefined;
1949
- index: number;
1950
- scrollProgress: number;
1951
- navigation: undefined;
1952
- };
1953
-
1954
- /**
1955
- * Scroll System - Public API Hook
1956
- * =======================================
1957
- * Facade principal para el uso de la librería.
1958
- * Expone una API estable y documentada.
1959
- */
1960
-
1961
- /**
1962
- * Hook principal para consumir el sistema de scroll.
1963
- * Retorna la API pública del sistema.
1964
- */
1965
- declare function useScrollSystem(): ScrollSystemAPI & {
1966
- isDragging: boolean;
1967
- isTransitioning: boolean;
1968
- };
1969
-
1970
- interface UseMetricsReporterOptions {
1971
- id: string;
1972
- isActive: boolean;
1973
- scrollDirection: ScrollDirection;
1974
- onScrollProgress?: (progress: number) => void;
1975
- /** Custom throttle interval (default: 66ms / ~15fps) */
1976
- throttleMs?: number;
1977
- }
1978
- declare function useMetricsReporter({ id, isActive, scrollDirection, onScrollProgress, throttleMs, }: UseMetricsReporterOptions): {
1979
- scrollRef: React$1.RefObject<HTMLDivElement>;
1980
- measureAndReport: () => void;
1981
- };
1982
-
1983
- declare const useScrollStore: zustand.UseBoundStore<Omit<zustand.StoreApi<ScrollSystemStore>, "subscribe"> & {
1984
- subscribe: {
1985
- (listener: (selectedState: ScrollSystemStore, previousSelectedState: ScrollSystemStore) => void): () => void;
1986
- <U>(selector: (state: ScrollSystemStore) => U, listener: (selectedState: U, previousSelectedState: U) => void, options?: {
1987
- equalityFn?: ((a: U, b: U) => boolean) | undefined;
1988
- fireImmediately?: boolean;
1989
- } | undefined): () => void;
1990
- };
1991
- }>;
1992
- declare const selectActiveView: (state: ScrollSystemStore) => ViewState;
1993
- declare const selectActiveViewProgress: (state: ScrollSystemStore) => number;
1994
- declare const selectCanNavigateNext: (state: ScrollSystemStore) => boolean;
1995
- declare const selectCanNavigatePrevious: (state: ScrollSystemStore) => boolean;
1996
-
1997
- /**
1998
- * PABRIX Scroll System - Constants
1999
- * =================================
2000
- */
2001
- declare const DEFAULT_TRANSITION_DURATION = 700;
2002
- declare const DEFAULT_TRANSITION_EASING = "cubic-bezier(0.645, 0.045, 0.355, 1.000)";
2003
- declare const DEFAULT_PROGRESS_DEBOUNCE = 16;
2004
- declare const NAVIGATION_COOLDOWN = 500;
2005
- declare const NAV_THRESHOLDS: {
2006
- WHEEL: number;
2007
- TOUCH: number;
2008
- };
2009
-
2010
- export { AriaLiveRegion$1 as AriaLiveRegion, type BaseViewConfig, type BaseViewProps, ControlledView$1 as ControlledView, type ControlledViewConfig, type ControlledViewProps, DEFAULT_PROGRESS_DEBOUNCE, DEFAULT_TRANSITION_DURATION, DEFAULT_TRANSITION_EASING, FullView$1 as FullView, type FullViewConfig, type FullViewProps, LazyView$1 as LazyView, NAVIGATION_COOLDOWN, NAV_THRESHOLDS, type NavigationState, type ScrollCapability, ScrollContainer$1 as ScrollContainer, type ScrollContainerProps, ScrollDebugOverlay$1 as ScrollDebugOverlay, type ScrollDirection, ScrollLockedView$1 as ScrollLockedView, type ScrollLockedViewConfig, type ScrollLockedViewProps, type ScrollSystemAPI, type ScrollSystemActions, type ScrollSystemState, type ScrollSystemStore, type UserIntention, type ViewConfig, type ViewMetrics, type ViewState, type ViewType, selectActiveView, selectActiveViewProgress, selectCanNavigateNext, selectCanNavigatePrevious, useActiveViewProgress, useDragHandler, useFocusManagement, useHashSync, useKeyboardHandler, useMetricsReporter, useNavigation$1 as useNavigation, useScrollAnalytics, useScrollStore, useScrollSystem, useTouchHandler, useViewControl$1 as useViewControl, useViewProgress, useViewRegistration, useWheelHandler };