react-sway 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,10 +1,48 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ // \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
2
23
  let react = require("react");
3
- let react_jsx_runtime = require("react/jsx-runtime");
4
- //#region src/edge-hover-velocity.ts
5
- /**
6
- * Defines the bounded spatial mapping from edge-hover depth to Sway velocity.
7
- */
24
+ react = __toESM(react, 1);
25
+ // src/depth-position.ts
26
+ var DEFAULT_DEPTH_PERSPECTIVE = 800;
27
+ var DEFAULT_DEPTH_SPACING = 420;
28
+ /** Bounds pixel lengths so periods and fade denominators remain finite and nonzero. */
29
+ function normalizeDepthLength(value, fallback) {
30
+ return Number.isFinite(value) && value > 0 ? Math.min(Number.MAX_SAFE_INTEGER, Math.max(1, value)) : fallback;
31
+ }
32
+ function getDepthItemState(position, index, count, perspective = 800, spacing = 420) {
33
+ if (count <= 0) return {
34
+ opacity: 0,
35
+ z: 0
36
+ };
37
+ const period = count * spacing;
38
+ const phase = ((index * spacing + position) % period + period) % period;
39
+ const fade = Math.min(1, Math.min(phase, period - phase) / (spacing * .45));
40
+ return {
41
+ opacity: fade * fade * (3 - 2 * fade),
42
+ z: perspective * .7 - phase
43
+ };
44
+ }
45
+ // src/edge-hover-velocity.ts
8
46
  /** Default edge-hover activation thickness in pixels. */
9
47
  var DEFAULT_EDGE_HOVER_SIZE = 96;
10
48
  /** Default maximum edge-hover speed relative to the configured base speed. */
@@ -50,10 +88,10 @@ function getEdgeHoverVelocityScale(intensity, speedMultiplier = 6) {
50
88
  if (depth < EDGE_HOVER_ENTRY_FEATHER_INTENSITY) return direction * smoothUnitStep(depth / EDGE_HOVER_ENTRY_FEATHER_INTENSITY);
51
89
  const physicalEdgeScale = Math.min(EDGE_HOVER_PHYSICAL_EDGE_SPEED_MULTIPLIER, maximumScale);
52
90
  if (depth <= EDGE_HOVER_PHYSICAL_EDGE_INTENSITY) {
53
- const physicalEdgeProgress = (depth - EDGE_HOVER_ENTRY_FEATHER_INTENSITY) / (EDGE_HOVER_PHYSICAL_EDGE_INTENSITY - EDGE_HOVER_ENTRY_FEATHER_INTENSITY);
91
+ const physicalEdgeProgress = (depth - EDGE_HOVER_ENTRY_FEATHER_INTENSITY) / .42;
54
92
  return direction * (1 + (physicalEdgeScale - 1) * smoothUnitStep(physicalEdgeProgress));
55
93
  }
56
- const beyondEdgeProgress = (depth - EDGE_HOVER_PHYSICAL_EDGE_INTENSITY) / (1 - EDGE_HOVER_PHYSICAL_EDGE_INTENSITY);
94
+ const beyondEdgeProgress = (depth - EDGE_HOVER_PHYSICAL_EDGE_INTENSITY) / .5;
57
95
  return direction * (physicalEdgeScale + (maximumScale - physicalEdgeScale) * smoothUnitStep(beyondEdgeProgress));
58
96
  }
59
97
  /**
@@ -79,9 +117,7 @@ function getEdgeHoverIntensity(pointerPosition, visibleInterval, edgeHoverSize)
79
117
  }
80
118
  return 0;
81
119
  }
82
- //#endregion
83
- //#region src/wheel-input.ts
84
- /** Fallback pixel height for wheel events reported in line units. */
120
+ // src/wheel-input.ts
85
121
  var WHEEL_LINE_HEIGHT_FALLBACK_PX = 16;
86
122
  /** WheelEvent deltaMode value for line-based deltas. */
87
123
  var WHEEL_MODE_LINE = 1;
@@ -115,8 +151,10 @@ function normalizeWheelDelta(event, container, axis) {
115
151
  * the gesture. This partial function is the wheel-ownership boundary.
116
152
  */
117
153
  function getOwnedWheelDelta(event, container, isHorizontal, wheelMode) {
154
+ if (event.ctrlKey) return null;
118
155
  const deltaX = normalizeWheelDelta(event, container, "x");
119
156
  const deltaY = normalizeWheelDelta(event, container, "y");
157
+ if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY) || deltaX === 0 && deltaY === 0) return null;
120
158
  const absDeltaX = Math.abs(deltaX);
121
159
  const absDeltaY = Math.abs(deltaY);
122
160
  if (isHorizontal) {
@@ -126,11 +164,7 @@ function getOwnedWheelDelta(event, container, isHorizontal, wheelMode) {
126
164
  if (wheelMode === "axis" && absDeltaX > absDeltaY) return null;
127
165
  return deltaY;
128
166
  }
129
- //#endregion
130
- //#region src/ReactSway.tsx
131
- /**
132
- * Core ReactSway component implementing axis-aware infinite scrolling interactions.
133
- */
167
+ // src/ReactSway.tsx
134
168
  /** Velocity applied per arrow key press (pixels). */
135
169
  var ARROW_KEY_VELOCITY = 15;
136
170
  /** Default friction coefficient applied to velocity each frame. */
@@ -161,6 +195,10 @@ var WHEEL_VELOCITY_MULTIPLIER = .14;
161
195
  var WHEEL_IMMEDIATE_DELTA_FACTOR = .14;
162
196
  /** Minimum touch movement before a gesture is classified by axis. */
163
197
  var TOUCH_AXIS_LOCK_THRESHOLD_PX = 8;
198
+ var INERT_ATTRIBUTE = { inert: Number.parseInt(react.version, 10) >= 19 ? true : "" };
199
+ function isInteractiveTarget(target) {
200
+ return target instanceof Element && Boolean(target.closest("a, button, input, select, textarea, [contenteditable]:not([contenteditable=\"false\"])"));
201
+ }
164
202
  function getVisibleEdgeInterval(rect, isHorizontal) {
165
203
  const rectEnd = isHorizontal ? rect.right : rect.bottom;
166
204
  const rectStart = isHorizontal ? rect.left : rect.top;
@@ -180,11 +218,11 @@ function getVisibleEdgeInterval(rect, isHorizontal) {
180
218
  * Renders children in a continuously looping scroll area with support for
181
219
  * auto-scrolling, mouse drag, touch swipe, wheel, keyboard interactions,
182
220
  * and optional edge-hover scrolling.
183
- * Content is duplicated to create a seamless loop effect. Duplicate content
221
+ * Planar content is duplicated to create a seamless loop effect. Duplicate content
184
222
  * is wrapped in `<aside>` elements with `aria-hidden="true"` for accessibility.
185
223
  *
186
- * Respects `prefers-reduced-motion: reduce` by lowering auto-scroll speed
187
- * and disabling momentum effects.
224
+ * Reduced motion stops depth auto-scroll, lowers planar auto-scroll speed,
225
+ * and disables momentum effects.
188
226
  *
189
227
  * @example
190
228
  * ```tsx
@@ -194,8 +232,14 @@ function getVisibleEdgeInterval(rect, isHorizontal) {
194
232
  * </ReactSway>
195
233
  * ```
196
234
  */
197
- var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollable content", axis, autoScroll = true, children, className, direction = "up", draggable = true, edgeHoverInputMode = "hybrid", edgeHoverScroll = false, edgeHoverSize = 96, edgeHoverSpeedMultiplier = 6, friction = DEFAULT_FRICTION, keyboard = true, lazy = true, lazyRootMargin = DEFAULT_LAZY_ROOT_MARGIN, lazyThreshold = DEFAULT_LAZY_THRESHOLD, onPause, onResume, onScroll, pauseOnInteraction = true, resumeDelay = DEFAULT_RESUME_DELAY, speed = DEFAULT_SPEED, wheelEnabled = true, wheelMode = "axis" }, ref) {
198
- const isHorizontal = (0, react.useMemo)(() => axis ?? (direction === "left" || direction === "right" ? "horizontal" : "vertical"), [axis, direction]) === "horizontal";
235
+ var ReactSway = (0, react.forwardRef)(function ReactSway({ active = true, ariaLabel = "Scrollable content", axis, autoScroll = true, children, className, depthPerspective = 800, depthSpacing = 420, direction = "up", draggable = true, edgeHoverInputMode = "hybrid", edgeHoverScroll = false, edgeHoverSize = 96, edgeHoverSpeedMultiplier = 6, friction = DEFAULT_FRICTION, keyboard = true, lazy = true, lazyRootMargin = DEFAULT_LAZY_ROOT_MARGIN, lazyThreshold = DEFAULT_LAZY_THRESHOLD, onPause, onResume, onScroll, pauseOnInteraction = true, reducedMotion = false, resumeDelay = DEFAULT_RESUME_DELAY, speed = DEFAULT_SPEED, wheelEnabled = true, wheelMode = "axis" }, ref) {
236
+ const scrollAxis = (0, react.useMemo)(() => axis ?? (direction === "toward" || direction === "away" ? "depth" : direction === "left" || direction === "right" ? "horizontal" : "vertical"), [axis, direction]);
237
+ const isHorizontal = scrollAxis === "horizontal";
238
+ const isDepth = scrollAxis === "depth";
239
+ const depthChildren = (0, react.useMemo)(() => react.Children.toArray(children), [children]);
240
+ const enabled = active && (!isDepth || depthChildren.length > 0);
241
+ const normalizedDepthPerspective = normalizeDepthLength(depthPerspective, 800);
242
+ const normalizedDepthSpacing = normalizeDepthLength(depthSpacing, 420);
199
243
  const normalizedEdgeHoverSize = (0, react.useMemo)(() => Number.isFinite(edgeHoverSize) ? Math.max(1, edgeHoverSize) : 96, [edgeHoverSize]);
200
244
  const normalizedEdgeHoverSpeedMultiplier = (0, react.useMemo)(() => normalizeEdgeHoverSpeedMultiplier(edgeHoverSpeedMultiplier), [edgeHoverSpeedMultiplier]);
201
245
  const normalizedFriction = (0, react.useMemo)(() => Number.isFinite(friction) ? Math.min(Math.max(friction, 0), 1) : DEFAULT_FRICTION, [friction]);
@@ -203,17 +247,22 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
203
247
  const normalizedResumeDelay = (0, react.useMemo)(() => Number.isFinite(resumeDelay) ? Math.max(0, resumeDelay) : DEFAULT_RESUME_DELAY, [resumeDelay]);
204
248
  const normalizedSpeed = (0, react.useMemo)(() => Number.isFinite(speed) ? Math.max(0, speed) : DEFAULT_SPEED, [speed]);
205
249
  const touchAction = (0, react.useMemo)(() => {
206
- if (!draggable) return "auto";
250
+ if (!enabled || !draggable) return "auto";
207
251
  return isHorizontal ? "pan-y" : "pan-x";
208
- }, [draggable, isHorizontal]);
209
- const overscrollBehavior = wheelEnabled && wheelMode === "capture" ? "contain" : "auto";
252
+ }, [
253
+ draggable,
254
+ enabled,
255
+ isHorizontal
256
+ ]);
257
+ const overscrollBehavior = enabled && wheelEnabled && wheelMode === "capture" ? "contain" : "auto";
210
258
  const [isPaused, setIsPaused] = (0, react.useState)(false);
211
259
  const [isTabActive, setIsTabActive] = (0, react.useState)(() => typeof document === "undefined" || !document.hidden);
212
260
  const [loopSegmentCount, setLoopSegmentCount] = (0, react.useState)(MIN_LOOP_SEGMENTS);
213
- const [prefersReducedMotion, setPrefersReducedMotion] = (0, react.useState)(() => {
261
+ const [systemReducedMotion, setSystemReducedMotion] = (0, react.useState)(() => {
214
262
  if (typeof window === "undefined") return false;
215
263
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
216
264
  });
265
+ const prefersReducedMotion = systemReducedMotion || reducedMotion;
217
266
  const animationFrameRef = (0, react.useRef)(null);
218
267
  const animationLoopRef = (0, react.useRef)(null);
219
268
  const autoScrollRef = (0, react.useRef)({
@@ -260,7 +309,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
260
309
  if (typeof window === "undefined") return;
261
310
  const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
262
311
  const handleChange = (e) => {
263
- setPrefersReducedMotion(e.matches);
312
+ setSystemReducedMotion(e.matches);
264
313
  };
265
314
  mediaQuery.addEventListener("change", handleChange);
266
315
  return () => {
@@ -300,11 +349,28 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
300
349
  }, [isHorizontal]);
301
350
  const renderPosition = (0, react.useCallback)((rawPosition) => {
302
351
  if (!containerRef.current) return;
352
+ if (isDepth) {
353
+ containerRef.current.style.transform = "translate3d(0, 0, 0)";
354
+ const items = originalGroupRef.current?.children;
355
+ if (!items) return;
356
+ Array.from(items).forEach((item, index) => {
357
+ const { opacity, z } = getDepthItemState(rawPosition, index, items.length, normalizedDepthPerspective, normalizedDepthSpacing);
358
+ const element = item;
359
+ element.style.transform = `translate3d(0, 0, ${z}px)`;
360
+ element.style.opacity = String(opacity);
361
+ });
362
+ return;
363
+ }
303
364
  const currentLoopPoint = loopPointRef.current;
304
365
  let visualPosition = rawPosition % (currentLoopPoint || 1);
305
366
  if (visualPosition > 0 && currentLoopPoint > 0) visualPosition -= currentLoopPoint;
306
367
  containerRef.current.style.transform = isHorizontal ? `translate3d(${visualPosition}px, 0, 0)` : `translate3d(0, ${visualPosition}px, 0)`;
307
- }, [isHorizontal]);
368
+ }, [
369
+ isDepth,
370
+ isHorizontal,
371
+ normalizedDepthPerspective,
372
+ normalizedDepthSpacing
373
+ ]);
308
374
  const commitPosition = (0, react.useCallback)((nextPosition) => {
309
375
  if (positionRef.current === nextPosition) return;
310
376
  positionRef.current = nextPosition;
@@ -325,11 +391,14 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
325
391
  }, [draggable]);
326
392
  const recalculateLoopPoint = (0, react.useCallback)(() => {
327
393
  if (!containerRef.current || !originalGroupRef.current) return;
328
- const originalContentSize = isHorizontal ? originalGroupRef.current.scrollWidth : originalGroupRef.current.scrollHeight;
329
- if (originalContentSize <= 0) return;
394
+ const originalContentSize = isDepth ? depthChildren.length * normalizedDepthSpacing : isHorizontal ? originalGroupRef.current.scrollWidth : originalGroupRef.current.scrollHeight;
395
+ if (originalContentSize <= 0) {
396
+ if (isDepth) loopPointRef.current = 0;
397
+ return;
398
+ }
330
399
  const viewport = containerRef.current.parentElement ?? containerRef.current;
331
400
  const viewportSize = isHorizontal ? Math.max(viewport.clientWidth, 1) : Math.max(viewport.clientHeight, 1);
332
- const nextLoopSegmentCount = Math.max(MIN_LOOP_SEGMENTS, Math.ceil(viewportSize / originalContentSize) + 1);
401
+ const nextLoopSegmentCount = isDepth ? 1 : Math.max(MIN_LOOP_SEGMENTS, Math.ceil(viewportSize / originalContentSize) + 1);
333
402
  const nextLoopPoint = originalContentSize;
334
403
  const previousMeasurement = lastLoopMeasurementRef.current;
335
404
  const nextMeasurement = {
@@ -342,7 +411,13 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
342
411
  setLoopSegmentCount((previousLoopSegmentCount) => previousLoopSegmentCount === nextLoopSegmentCount ? previousLoopSegmentCount : nextLoopSegmentCount);
343
412
  loopPointRef.current = nextLoopPoint;
344
413
  renderPosition(positionRef.current);
345
- }, [isHorizontal, renderPosition]);
414
+ }, [
415
+ depthChildren.length,
416
+ isDepth,
417
+ isHorizontal,
418
+ normalizedDepthSpacing,
419
+ renderPosition
420
+ ]);
346
421
  (0, react.useEffect)(() => {
347
422
  autoScrollRef.current.desired = autoScroll;
348
423
  if (!autoScroll) {
@@ -367,6 +442,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
367
442
  scheduleLoopPointRecalculation();
368
443
  }, [
369
444
  children,
445
+ isDepth,
370
446
  isHorizontal,
371
447
  scheduleLoopPointRecalculation
372
448
  ]);
@@ -421,7 +497,14 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
421
497
  touchDragListenerCleanupRef.current = null;
422
498
  }, []);
423
499
  const handleKeyDown = (0, react.useCallback)((e) => {
424
- if (!keyboard) return;
500
+ if (!enabled || !keyboard || isInteractiveTarget(e.target)) return;
501
+ const arrowDelta = isHorizontal ? e.key === "ArrowLeft" ? ARROW_KEY_VELOCITY : e.key === "ArrowRight" ? -15 : 0 : e.key === "ArrowUp" ? ARROW_KEY_VELOCITY : e.key === "ArrowDown" ? -15 : 0;
502
+ if (prefersReducedMotion && arrowDelta !== 0) {
503
+ e.preventDefault();
504
+ commitPosition(wrapPosition(positionRef.current + arrowDelta));
505
+ velocityRef.current = 0;
506
+ return;
507
+ }
425
508
  switch (e.key) {
426
509
  case " ":
427
510
  e.preventDefault();
@@ -468,18 +551,19 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
468
551
  velocityRef.current = 0;
469
552
  pauseAutoScroll();
470
553
  scheduleAutoScrollResume();
471
- break;
472
- default: break;
473
554
  }
474
555
  startAnimationLoop();
475
556
  }, [
476
557
  commitPosition,
558
+ enabled,
477
559
  isHorizontal,
478
560
  keyboard,
479
561
  pauseAutoScroll,
562
+ prefersReducedMotion,
480
563
  scheduleAutoScrollResume,
481
564
  startAnimationLoop,
482
- togglePause
565
+ togglePause,
566
+ wrapPosition
483
567
  ]);
484
568
  const handleMouseMove = (0, react.useCallback)((e) => {
485
569
  if (!isDraggingRef.current) return;
@@ -537,7 +621,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
537
621
  handleMouseUp
538
622
  ]);
539
623
  const handleMouseDown = (0, react.useCallback)((e) => {
540
- if (!draggable) return;
624
+ if (!enabled || !draggable || e.button !== 0 || isInteractiveTarget(e.target)) return;
541
625
  e.preventDefault();
542
626
  containerRef.current?.focus();
543
627
  setDraggingState(true);
@@ -550,6 +634,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
550
634
  }, [
551
635
  attachMouseDragListeners,
552
636
  draggable,
637
+ enabled,
553
638
  pauseAutoScroll,
554
639
  setDraggingState,
555
640
  startAnimationLoop
@@ -573,6 +658,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
573
658
  if (touchInteractionStateRef.current === "idle" || touchInteractionStateRef.current === "ignored") return;
574
659
  if (e.touches.length !== 1) return;
575
660
  const touch = e.touches[0];
661
+ if (!touch) return;
576
662
  if (touchInteractionStateRef.current === "pending") {
577
663
  const totalDeltaX = touch.clientX - touchStartXRef.current;
578
664
  const totalDeltaY = touch.clientY - touchStartYRef.current;
@@ -617,18 +703,24 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
617
703
  handleTouchMove
618
704
  ]);
619
705
  const handleTouchStart = (0, react.useCallback)((e) => {
620
- if (!draggable || e.touches.length !== 1) return;
706
+ if (!enabled || !draggable || e.touches.length !== 1 || isInteractiveTarget(e.target)) return;
707
+ const touch = e.touches[0];
708
+ if (!touch) return;
621
709
  containerRef.current?.focus();
622
710
  touchInteractionStateRef.current = "pending";
623
- lastTouchXRef.current = e.touches[0].clientX;
624
- lastTouchYRef.current = e.touches[0].clientY;
625
- touchStartXRef.current = e.touches[0].clientX;
626
- touchStartYRef.current = e.touches[0].clientY;
711
+ lastTouchXRef.current = touch.clientX;
712
+ lastTouchYRef.current = touch.clientY;
713
+ touchStartXRef.current = touch.clientX;
714
+ touchStartYRef.current = touch.clientY;
627
715
  velocityRef.current = 0;
628
716
  attachTouchDragListeners();
629
- }, [attachTouchDragListeners, draggable]);
717
+ }, [
718
+ attachTouchDragListeners,
719
+ draggable,
720
+ enabled
721
+ ]);
630
722
  const handleWheel = (0, react.useCallback)((e) => {
631
- if (!wheelEnabled) return false;
723
+ if (!enabled || !wheelEnabled || e.defaultPrevented) return false;
632
724
  const currentContainer = e.currentTarget instanceof HTMLElement ? e.currentTarget : containerRef.current;
633
725
  if (!currentContainer) return false;
634
726
  const ownedDelta = getOwnedWheelDelta(e, currentContainer, isHorizontal, wheelMode);
@@ -645,6 +737,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
645
737
  return true;
646
738
  }, [
647
739
  commitPosition,
740
+ enabled,
648
741
  isHorizontal,
649
742
  pauseAutoScroll,
650
743
  scheduleAutoScrollResume,
@@ -654,7 +747,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
654
747
  wrapPosition
655
748
  ]);
656
749
  const applyEdgeHoverPoint = (0, react.useCallback)((point) => {
657
- if (!edgeHoverScroll) return false;
750
+ if (!enabled || !edgeHoverScroll) return false;
658
751
  const viewport = containerRef.current?.parentElement ?? containerRef.current;
659
752
  if (!viewport) return false;
660
753
  const visibleInterval = readVisibleEdgeInterval(viewport);
@@ -667,6 +760,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
667
760
  return true;
668
761
  }, [
669
762
  edgeHoverScroll,
763
+ enabled,
670
764
  isHorizontal,
671
765
  normalizedEdgeHoverSize,
672
766
  readVisibleEdgeInterval,
@@ -681,6 +775,9 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
681
775
  externalEdgeHoverLeaseRef.current = false;
682
776
  setEdgeHoverIntensity(0);
683
777
  }, [setEdgeHoverIntensity]);
778
+ (0, react.useEffect)(() => {
779
+ if (!edgeHoverScroll) clearEdgeHover();
780
+ }, [clearEdgeHover, edgeHoverScroll]);
684
781
  (0, react.useImperativeHandle)(ref, () => ({
685
782
  clearEdgeHover,
686
783
  handleEdgeHover,
@@ -700,7 +797,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
700
797
  }, [setEdgeHoverIntensity]);
701
798
  (0, react.useEffect)(() => {
702
799
  const currentContainer = containerRef.current;
703
- if (!currentContainer) return;
800
+ if (!currentContainer || !enabled) return;
704
801
  if (draggable) {
705
802
  currentContainer.addEventListener("mousedown", handleMouseDown);
706
803
  currentContainer.addEventListener("touchstart", handleTouchStart, { passive: true });
@@ -738,6 +835,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
738
835
  detachMouseDragListeners,
739
836
  detachTouchDragListeners,
740
837
  draggable,
838
+ enabled,
741
839
  edgeHoverScroll,
742
840
  edgeHoverInputMode,
743
841
  clearEdgeHover,
@@ -748,6 +846,24 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
748
846
  handleWheel,
749
847
  wheelEnabled
750
848
  ]);
849
+ (0, react.useEffect)(() => {
850
+ if (enabled) return;
851
+ clearInactivityTimer();
852
+ clearEdgeHover();
853
+ detachMouseDragListeners();
854
+ detachTouchDragListeners();
855
+ touchInteractionStateRef.current = "idle";
856
+ setDraggingState(false);
857
+ velocityRef.current = 0;
858
+ autoScrollRef.current.active = autoScrollRef.current.desired;
859
+ }, [
860
+ clearEdgeHover,
861
+ clearInactivityTimer,
862
+ detachMouseDragListeners,
863
+ detachTouchDragListeners,
864
+ enabled,
865
+ setDraggingState
866
+ ]);
751
867
  (0, react.useEffect)(() => {
752
868
  window.addEventListener("resize", scheduleLoopPointRecalculation);
753
869
  return () => {
@@ -796,7 +912,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
796
912
  };
797
913
  }, [clearInactivityTimer]);
798
914
  (0, react.useEffect)(() => {
799
- if (!isTabActive || isPaused) {
915
+ if (!enabled || !isTabActive || isPaused) {
800
916
  if (animationFrameRef.current !== null) {
801
917
  cancelAnimationFrame(animationFrameRef.current);
802
918
  animationFrameRef.current = null;
@@ -805,7 +921,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
805
921
  return;
806
922
  }
807
923
  lastFrameTimeRef.current = 0;
808
- const directionMultiplier = direction === "down" || direction === "right" ? 1 : -1;
924
+ const directionMultiplier = direction === "away" || direction === "down" || direction === "right" ? 1 : -1;
809
925
  const animate = (currentTime) => {
810
926
  let deltaTime = lastFrameTimeRef.current ? (currentTime - lastFrameTimeRef.current) / MS_PER_FRAME_60FPS : 1;
811
927
  deltaTime = Math.min(deltaTime, MAX_DELTA_TIME);
@@ -816,7 +932,7 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
816
932
  let nextPosition = positionRef.current;
817
933
  const effectiveSpeed = prefersReducedMotion ? normalizedSpeed * REDUCED_MOTION_SPEED_FACTOR : normalizedSpeed;
818
934
  const edgeHoverMultiplier = edgeHoverScroll ? getEdgeHoverVelocityScale(edgeHoverIntensityRef.current, normalizedEdgeHoverSpeedMultiplier) : directionMultiplier;
819
- const shouldAutoScroll = (autoScrollRef.current.desired && autoScrollRef.current.active || externalEdgeHoverLeaseRef.current) && !isDraggingRef.current && (!edgeHoverScroll || edgeHoverMultiplier !== 0);
935
+ const shouldAutoScroll = (autoScrollRef.current.desired && autoScrollRef.current.active || externalEdgeHoverLeaseRef.current) && !isDraggingRef.current && !(isDepth && prefersReducedMotion) && (!edgeHoverScroll || edgeHoverMultiplier !== 0);
820
936
  if (shouldAutoScroll) {
821
937
  const autoScrollDeltaTime = edgeHoverScroll ? Math.min(deltaTime, MAX_EDGE_HOVER_DELTA_TIME) : deltaTime;
822
938
  nextPosition += edgeHoverMultiplier * effectiveSpeed * autoScrollDeltaTime;
@@ -845,6 +961,8 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
845
961
  commitPosition,
846
962
  direction,
847
963
  edgeHoverScroll,
964
+ enabled,
965
+ isDepth,
848
966
  isPaused,
849
967
  isTabActive,
850
968
  normalizedFriction,
@@ -877,56 +995,77 @@ var ReactSway = (0, react.forwardRef)(function ReactSway({ ariaLabel = "Scrollab
877
995
  lazyRootMargin,
878
996
  normalizedLazyThreshold
879
997
  ]);
880
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
998
+ return /* @__PURE__ */ react.default.createElement("section", {
881
999
  "aria-label": ariaLabel,
882
1000
  className: `react-sway-container scroller-content${className ? ` ${className}` : ""}`,
883
1001
  ref: containerRef,
884
1002
  style: {
885
- cursor: draggable ? isDraggingRef.current ? "grabbing" : "grab" : "default",
1003
+ cursor: enabled && draggable ? isDraggingRef.current ? "grabbing" : "grab" : "default",
886
1004
  display: isHorizontal ? "flex" : void 0,
887
1005
  flexDirection: isHorizontal ? "row" : void 0,
888
- height: isHorizontal ? "100%" : "max-content",
1006
+ height: isHorizontal || isDepth ? "100%" : "max-content",
889
1007
  MozUserSelect: "none",
890
1008
  msUserSelect: "none",
891
1009
  minHeight: isHorizontal ? void 0 : "100%",
892
- overflow: "hidden",
1010
+ overflow: isDepth ? "visible" : "hidden",
1011
+ perspective: isDepth ? normalizedDepthPerspective : void 0,
893
1012
  overscrollBehavior,
894
1013
  pointerEvents: "auto",
895
1014
  position: "absolute",
896
1015
  touchAction,
897
1016
  transform: "translate3d(0, 0, 0)",
1017
+ transformStyle: isDepth ? "preserve-3d" : void 0,
898
1018
  userSelect: "none",
899
1019
  WebkitUserSelect: "none",
900
1020
  width: isHorizontal ? "max-content" : "100%",
901
- willChange: "transform",
1021
+ willChange: isDepth ? void 0 : "transform",
902
1022
  zIndex: 1
903
1023
  },
904
1024
  onKeyDown: keyboard ? handleKeyDown : void 0,
905
- tabIndex: keyboard ? 0 : void 0,
906
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
907
- className: "content-group original",
908
- ref: originalGroupRef,
909
- style: isHorizontal ? {
910
- display: "flex",
911
- flex: "0 0 auto"
912
- } : void 0,
913
- children
914
- }), Array.from({ length: loopSegmentCount - 1 }, (_, duplicateIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("aside", {
915
- "aria-hidden": "true",
916
- className: "content-group duplicate",
917
- "data-duplicate": "true",
918
- inert: true,
919
- role: "presentation",
920
- style: isHorizontal ? {
921
- display: "flex",
922
- flex: "0 0 auto",
923
- pointerEvents: "none"
924
- } : { pointerEvents: "none" },
925
- children
926
- }, `duplicate-${duplicateIndex}`))]
927
- });
1025
+ tabIndex: enabled && keyboard ? 0 : void 0
1026
+ }, /* @__PURE__ */ react.default.createElement("div", {
1027
+ className: "content-group original",
1028
+ ref: originalGroupRef,
1029
+ style: isDepth ? {
1030
+ height: "100%",
1031
+ position: "relative",
1032
+ transformStyle: "preserve-3d"
1033
+ } : isHorizontal ? {
1034
+ display: "flex",
1035
+ flex: "0 0 auto"
1036
+ } : void 0
1037
+ }, isDepth ? depthChildren.map((child, index) => {
1038
+ const { opacity, z } = getDepthItemState(0, index, depthChildren.length, normalizedDepthPerspective, normalizedDepthSpacing);
1039
+ return /* @__PURE__ */ react.default.createElement("div", {
1040
+ className: "react-sway-depth-item",
1041
+ key: typeof child === "object" && "key" in child ? child.key : index,
1042
+ style: {
1043
+ height: 0,
1044
+ left: "50%",
1045
+ opacity,
1046
+ position: "absolute",
1047
+ top: "50%",
1048
+ transform: `translate3d(0, 0, ${z}px)`,
1049
+ width: 0,
1050
+ willChange: "transform, opacity"
1051
+ }
1052
+ }, child);
1053
+ }) : children), Array.from({ length: isDepth ? 0 : loopSegmentCount - 1 }, (_, duplicateIndex) => /* @__PURE__ */ react.default.createElement("aside", {
1054
+ "aria-hidden": "true",
1055
+ className: "content-group duplicate",
1056
+ "data-duplicate": "true",
1057
+ ...INERT_ATTRIBUTE,
1058
+ key: `duplicate-${duplicateIndex}`,
1059
+ role: "presentation",
1060
+ style: isHorizontal ? {
1061
+ display: "flex",
1062
+ flex: "0 0 auto",
1063
+ pointerEvents: "none"
1064
+ } : { pointerEvents: "none" }
1065
+ }, children)));
928
1066
  });
929
- //#endregion
1067
+ exports.DEFAULT_DEPTH_PERSPECTIVE = DEFAULT_DEPTH_PERSPECTIVE;
1068
+ exports.DEFAULT_DEPTH_SPACING = DEFAULT_DEPTH_SPACING;
930
1069
  exports.DEFAULT_EDGE_HOVER_SIZE = DEFAULT_EDGE_HOVER_SIZE;
931
1070
  exports.DEFAULT_EDGE_HOVER_SPEED_MULTIPLIER = DEFAULT_EDGE_HOVER_SPEED_MULTIPLIER;
932
1071
  exports.ReactSway = ReactSway;
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@
2
2
  * Public package entrypoint for ReactSway exports.
3
3
  */
4
4
  export { default as ReactSway } from './ReactSway';
5
+ export { DEFAULT_DEPTH_PERSPECTIVE, DEFAULT_DEPTH_SPACING, } from './depth-position';
5
6
  export { DEFAULT_EDGE_HOVER_SIZE, DEFAULT_EDGE_HOVER_SPEED_MULTIPLIER, } from './edge-hover-velocity';
6
7
  export type { ReactSwayHandle, ReactSwayProps, SwayAxis, SwayDirection, SwayEdgeHoverInputMode, SwayWheelMode, } from './ReactSway';