use-scroller 2.0.0 → 2.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.
package/README.md CHANGED
@@ -104,7 +104,8 @@ It sets `touch-action: pan-y pinch-zoom` on the track (`pan-x pinch-zoom` for `a
104
104
  cross-axis movement scrolls the page natively and pinch-zoom keeps working. A gesture
105
105
  is judged after 8px: mostly sideways and the track follows the finger, otherwise it is left to the
106
106
  browser. Release continues with momentum and lands on a CSS scroll-snap position if the track has
107
- any. A drag doesn't fire a click on whatever is under the finger. Wheel, trackpad, keyboard and the scrollbar stay
107
+ any. Like native scrolling, it never carries past a snap position whose area has
108
+ `scroll-snap-stop: always`. A drag doesn't fire a click on whatever is under the finger. Wheel, trackpad, keyboard and the scrollbar stay
108
109
  native. `mouse: true` also lets a mouse drag the track.
109
110
 
110
111
  ### `useScrollLock`
@@ -298,6 +299,7 @@ const stop = observeActiveSection(sections, (index) => highlight(index), {
298
299
  readScroll(target) // { x, y, minX, maxX, maxY, viewportWidth, viewportHeight }
299
300
  isScrollable(element, 'y')
300
301
  findScrollParent(element, 'y') // nearest scrollable ancestor, or window
302
+ readSnapPositions(element, 'x') // CSS scroll-snap positions the container can rest at, ascending
301
303
  ```
302
304
 
303
305
  ## Recipes
@@ -431,11 +433,12 @@ names its axis, and the package is ESM-only.
431
433
  | `createEasingAnimation`, `createMomentumAnimation` | Removed. Use `animateScroll` |
432
434
  | Types `ScrollOptions`, `EasingOptions`, `UseScrollReturn` | `AnimateScrollOptions`, `ScrollAnimation`, `Easing`, `ScrollActions`, `ScrollState` |
433
435
 
434
- ## Example app
436
+ ## Demo site
435
437
 
436
- `example/` has a demo of every feature, with native and enhanced versions side by side for the touch
437
- ones. Run `npm install && npm run dev` inside it; the dev server listens on your LAN so you can open
438
- it on a phone.
438
+ [silverlila.github.io/use-scroller](https://silverlila.github.io/use-scroller/) walks through every
439
+ export with its code and a live demo. The source is in `site/`: run `yarn` at the root, then
440
+ `npm install && npm run dev` inside `site/`; the dev server listens on your LAN so you can open it on
441
+ a phone.
439
442
 
440
443
  ## License
441
444
 
@@ -600,21 +600,25 @@ function fling(target, axis, velocity) {
600
600
  // src/core/snap.ts
601
601
  var FLICK_VELOCITY = 300;
602
602
  function readSnapPositions(container, axis) {
603
+ return readSnapPoints(container, axis).map(({ position }) => position);
604
+ }
605
+ function readSnapPoints(container, axis) {
603
606
  const containerStyle = getComputedStyle(container);
604
607
  if (containerStyle.scrollSnapType === "none") return [];
605
608
  const rightToLeft = axis === "x" && containerStyle.direction === "rtl";
606
609
  const port = scrollport(container, axis);
607
610
  const origin = contentOrigin(container, axis);
608
611
  const metrics = readScroll(container);
609
- const positions = /* @__PURE__ */ new Set();
612
+ const stops = /* @__PURE__ */ new Map();
610
613
  for (const [child, style] of snapAreaCandidates(container)) {
611
614
  const logical = snapAlignment(style.scrollSnapAlign, axis);
612
615
  if (logical === null) continue;
613
616
  const alignment = rightToLeft ? mirrored(logical) : logical;
614
617
  const area = snapArea(child, style, origin, axis);
615
- positions.add(clampToRange(metrics, axis, alignedPosition(area, port, alignment)));
618
+ const position = clampToRange(metrics, axis, alignedPosition(area, port, alignment));
619
+ stops.set(position, stops.get(position) === true || style.scrollSnapStop === "always");
616
620
  }
617
- return [...positions].sort((a, b) => a - b);
621
+ return [...stops].map(([position, stop]) => ({ position, stop })).sort((a, b) => a.position - b.position);
618
622
  }
619
623
  function* snapAreaCandidates(root) {
620
624
  for (const child of root.children) {
@@ -636,6 +640,14 @@ function pickSnapTarget(positions, current, projected, velocity) {
636
640
  if (ahead.length === 0) return nearest;
637
641
  return direction > 0 ? ahead[0] : ahead[ahead.length - 1];
638
642
  }
643
+ function stopShortOfSnapStops(points, current, target) {
644
+ const direction = Math.sign(target - current);
645
+ const passed = points.filter(
646
+ ({ position, stop }) => stop && (position - current) * direction > EDGE_TOLERANCE && (target - position) * direction > 0
647
+ );
648
+ if (passed.length === 0) return target;
649
+ return direction > 0 ? passed[0].position : passed[passed.length - 1].position;
650
+ }
639
651
  function snapAlignment(value, axis) {
640
652
  const [block, inline = block] = value.split(" ");
641
653
  const keyword = axis === "x" ? inline : block;
@@ -732,9 +744,9 @@ function dragScroll(element, { axis = "x", mouse = false } = {}) {
732
744
  }
733
745
  function interruptRelease() {
734
746
  if (!release) return null;
735
- const { handle, positions } = release;
747
+ const { handle, points } = release;
736
748
  handle.cancel();
737
- return { positions, restore: suspendSnap2() };
749
+ return { points, restore: suspendSnap2() };
738
750
  }
739
751
  function suspendSnap2() {
740
752
  return setInlineStyles(element, { "scroll-snap-type": "none" });
@@ -763,7 +775,7 @@ function dragScroll(element, { axis = "x", mouse = false } = {}) {
763
775
  element.setPointerCapture(event.pointerId);
764
776
  cancelScroll(element);
765
777
  const snap = (_a = pending.snap) != null ? _a : {
766
- positions: readSnapPositions(element, axis),
778
+ points: readSnapPoints(element, axis),
767
779
  restore: suspendSnap2()
768
780
  };
769
781
  const startScroll = readScroll(element)[axis];
@@ -797,24 +809,26 @@ function dragScroll(element, { axis = "x", mouse = false } = {}) {
797
809
  if ((ended == null ? void 0 : ended.kind) === "dragging") ended.restoreUserSelect();
798
810
  if (ended == null ? void 0 : ended.snap) settle(ended.snap, velocity);
799
811
  }
800
- function settle({ positions, restore }, velocity) {
801
- if (positions.length === 0) {
812
+ function settle({ points, restore }, velocity) {
813
+ if (points.length === 0) {
802
814
  restore();
803
- track(fling(element, axis, velocity), positions);
815
+ track(fling(element, axis, velocity), points);
804
816
  return;
805
817
  }
806
818
  const current = readScroll(element)[axis];
807
- const target = pickSnapTarget(positions, current, current + projectMomentum(velocity), velocity);
819
+ const positions = points.map(({ position }) => position);
820
+ const picked = pickSnapTarget(positions, current, current + projectMomentum(velocity), velocity);
821
+ const target = stopShortOfSnapStops(points, current, picked);
808
822
  restore();
809
823
  const handle = animateScroll(element, axis === "x" ? { x: target } : { y: target }, {
810
824
  animation: { type: "spring", velocity: axis === "x" ? { x: velocity } : { y: velocity } },
811
825
  respectReducedMotion: false
812
826
  });
813
- track(handle, positions);
827
+ track(handle, points);
814
828
  }
815
- function track(handle, positions) {
829
+ function track(handle, points) {
816
830
  if (!handle) return;
817
- const current = { handle, positions };
831
+ const current = { handle, points };
818
832
  release = current;
819
833
  void handle.finished.then(() => {
820
834
  if (release === current) release = null;
@@ -1178,6 +1192,7 @@ export {
1178
1192
  initialScrollState,
1179
1193
  observeScroll,
1180
1194
  lockScroll,
1195
+ readSnapPositions,
1181
1196
  dragScroll,
1182
1197
  scrollToEdge,
1183
1198
  scrollBy,
package/dist/core.d.ts CHANGED
@@ -91,6 +91,8 @@ interface DragScrollOptions {
91
91
  }
92
92
  declare function dragScroll(element: HTMLElement, { axis, mouse }?: DragScrollOptions): () => void;
93
93
 
94
+ declare function readSnapPositions(container: HTMLElement, axis: Axis): number[];
95
+
94
96
  declare function observeActiveSection(sections: HTMLElement[], listener: (index: number) => void, { root, offset }?: {
95
97
  root?: ScrollTarget;
96
98
  offset?: number;
@@ -118,4 +120,4 @@ interface StickToBottom {
118
120
  }
119
121
  declare function stickToBottom(element: HTMLElement, onChange?: (isAtBottom: boolean) => void): StickToBottom;
120
122
 
121
- export { type AnimateScrollOptions, type Axis, type DragScrollOptions, type Easing, type Edge, type RestoreScrollOptions, type ScrollAlign, type ScrollAnimation, type ScrollDestination, type ScrollHandle, type ScrollMetrics, type ScrollResult, type ScrollState, type ScrollTarget, type ScrollToElementOptions, type StickToBottom, animateScroll, cancelScroll, cubicBezier, dragScroll, easings, findScrollParent, isScrollable, lockScroll, observeActiveSection, observeScroll, readScroll, restoreScroll, scrollBy, scrollToEdge, scrollToElement, stickToBottom };
123
+ export { type AnimateScrollOptions, type Axis, type DragScrollOptions, type Easing, type Edge, type RestoreScrollOptions, type ScrollAlign, type ScrollAnimation, type ScrollDestination, type ScrollHandle, type ScrollMetrics, type ScrollResult, type ScrollState, type ScrollTarget, type ScrollToElementOptions, type StickToBottom, animateScroll, cancelScroll, cubicBezier, dragScroll, easings, findScrollParent, isScrollable, lockScroll, observeActiveSection, observeScroll, readScroll, readSnapPositions, restoreScroll, scrollBy, scrollToEdge, scrollToElement, stickToBottom };
package/dist/core.js CHANGED
@@ -10,12 +10,13 @@ import {
10
10
  observeActiveSection,
11
11
  observeScroll,
12
12
  readScroll,
13
+ readSnapPositions,
13
14
  restoreScroll,
14
15
  scrollBy,
15
16
  scrollToEdge,
16
17
  scrollToElement,
17
18
  stickToBottom
18
- } from "./chunk-BDIJT5M4.js";
19
+ } from "./chunk-IFT3KMZJ.js";
19
20
  export {
20
21
  animateScroll,
21
22
  cancelScroll,
@@ -28,6 +29,7 @@ export {
28
29
  observeActiveSection,
29
30
  observeScroll,
30
31
  readScroll,
32
+ readSnapPositions,
31
33
  restoreScroll,
32
34
  scrollBy,
33
35
  scrollToEdge,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ScrollDestination, AnimateScrollOptions, ScrollHandle, Edge, ScrollToElementOptions, ScrollState, DragScrollOptions, RestoreScrollOptions } from './core.js';
2
- export { Axis, Easing, ScrollAlign, ScrollAnimation, ScrollMetrics, ScrollResult, ScrollTarget, StickToBottom, animateScroll, cancelScroll, cubicBezier, dragScroll, easings, findScrollParent, isScrollable, lockScroll, observeActiveSection, observeScroll, readScroll, restoreScroll, scrollBy, scrollToEdge, scrollToElement, stickToBottom } from './core.js';
2
+ export { Axis, Easing, ScrollAlign, ScrollAnimation, ScrollMetrics, ScrollResult, ScrollTarget, StickToBottom, animateScroll, cancelScroll, cubicBezier, dragScroll, easings, findScrollParent, isScrollable, lockScroll, observeActiveSection, observeScroll, readScroll, readSnapPositions, restoreScroll, scrollBy, scrollToEdge, scrollToElement, stickToBottom } from './core.js';
3
3
  import { RefObject } from 'react';
4
4
 
5
5
  type ElementTarget = RefObject<HTMLElement | null> | 'window';
package/dist/index.js CHANGED
@@ -13,12 +13,13 @@ import {
13
13
  observeActiveSection,
14
14
  observeScroll,
15
15
  readScroll,
16
+ readSnapPositions,
16
17
  restoreScroll,
17
18
  scrollBy,
18
19
  scrollToEdge,
19
20
  scrollToElement,
20
21
  stickToBottom
21
- } from "./chunk-BDIJT5M4.js";
22
+ } from "./chunk-IFT3KMZJ.js";
22
23
 
23
24
  // src/react/use-scroll.ts
24
25
  import { useRef as useRef2, useState } from "react";
@@ -330,6 +331,7 @@ export {
330
331
  observeActiveSection,
331
332
  observeScroll,
332
333
  readScroll,
334
+ readSnapPositions,
333
335
  restoreScroll,
334
336
  scrollBy,
335
337
  scrollToEdge,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-scroller",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "author": "silvi lila",
5
5
  "license": "MIT",
6
6
  "type": "module",