• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JedWatson / react-select / 36c10f1a-e614-479e-8065-a6ee4ffe0d2e

11 Dec 2024 10:57PM UTC coverage: 75.844%. Remained the same
36c10f1a-e614-479e-8065-a6ee4ffe0d2e

Pull #5984

circleci

web-flow
Create five-ligers-beg.md
Pull Request #5984: Add peer version to include 19

658 of 1052 branches covered (62.55%)

1033 of 1362 relevant lines covered (75.84%)

1934.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.0
/packages/react-select/src/internal/useScrollCapture.ts
1
import { useCallback, useEffect, useRef } from 'react';
2
import { supportsPassiveEvents } from '../utils';
3

4
const cancelScroll = (event: WheelEvent | TouchEvent) => {
5✔
5
  if (event.cancelable) event.preventDefault();
×
6
  event.stopPropagation();
×
7
};
8

9
interface Options {
10
  readonly isEnabled: boolean;
11
  readonly onBottomArrive?: (event: WheelEvent | TouchEvent) => void;
12
  readonly onBottomLeave?: (event: WheelEvent | TouchEvent) => void;
13
  readonly onTopArrive?: (event: WheelEvent | TouchEvent) => void;
14
  readonly onTopLeave?: (event: WheelEvent | TouchEvent) => void;
15
}
16

17
export default function useScrollCapture({
18
  isEnabled,
19
  onBottomArrive,
20
  onBottomLeave,
21
  onTopArrive,
22
  onTopLeave,
23
}: Options) {
24
  const isBottom = useRef(false);
768✔
25
  const isTop = useRef(false);
768✔
26
  const touchStart = useRef(0);
768✔
27
  const scrollTarget = useRef<HTMLElement | null>(null);
768✔
28

29
  const handleEventDelta = useCallback(
768✔
30
    (event: WheelEvent | TouchEvent, delta: number) => {
31
      if (scrollTarget.current === null) return;
×
32

33
      const { scrollTop, scrollHeight, clientHeight } = scrollTarget.current;
×
34
      const target = scrollTarget.current;
×
35
      const isDeltaPositive = delta > 0;
×
36
      const availableScroll = scrollHeight - clientHeight - scrollTop;
×
37
      let shouldCancelScroll = false;
×
38

39
      // reset bottom/top flags
40
      if (availableScroll > delta && isBottom.current) {
×
41
        if (onBottomLeave) onBottomLeave(event);
×
42
        isBottom.current = false;
×
43
      }
44
      if (isDeltaPositive && isTop.current) {
×
45
        if (onTopLeave) onTopLeave(event);
×
46
        isTop.current = false;
×
47
      }
48

49
      // bottom limit
50
      if (isDeltaPositive && delta > availableScroll) {
×
51
        if (onBottomArrive && !isBottom.current) {
×
52
          onBottomArrive(event);
×
53
        }
54
        target.scrollTop = scrollHeight;
×
55
        shouldCancelScroll = true;
×
56
        isBottom.current = true;
×
57

58
        // top limit
59
      } else if (!isDeltaPositive && -delta > scrollTop) {
×
60
        if (onTopArrive && !isTop.current) {
×
61
          onTopArrive(event);
×
62
        }
63
        target.scrollTop = 0;
×
64
        shouldCancelScroll = true;
×
65
        isTop.current = true;
×
66
      }
67

68
      // cancel scroll
69
      if (shouldCancelScroll) {
×
70
        cancelScroll(event);
×
71
      }
72
    },
73
    [onBottomArrive, onBottomLeave, onTopArrive, onTopLeave]
74
  );
75

76
  const onWheel = useCallback(
768✔
77
    (event: WheelEvent) => {
78
      handleEventDelta(event, event.deltaY);
×
79
    },
80
    [handleEventDelta]
81
  );
82
  const onTouchStart = useCallback((event: TouchEvent) => {
768✔
83
    // set touch start so we can calculate touchmove delta
84
    touchStart.current = event.changedTouches[0].clientY;
×
85
  }, []);
86
  const onTouchMove = useCallback(
768✔
87
    (event: TouchEvent) => {
88
      const deltaY = touchStart.current - event.changedTouches[0].clientY;
×
89
      handleEventDelta(event, deltaY);
×
90
    },
91
    [handleEventDelta]
92
  );
93

94
  const startListening = useCallback(
768✔
95
    (el) => {
96
      // bail early if no element is available to attach to
97
      if (!el) return;
×
98

99
      const notPassive = supportsPassiveEvents ? { passive: false } : false;
×
100
      el.addEventListener('wheel', onWheel, notPassive);
×
101
      el.addEventListener('touchstart', onTouchStart, notPassive);
×
102
      el.addEventListener('touchmove', onTouchMove, notPassive);
×
103
    },
104
    [onTouchMove, onTouchStart, onWheel]
105
  );
106

107
  const stopListening = useCallback(
768✔
108
    (el) => {
109
      // bail early if no element is available to detach from
110
      if (!el) return;
×
111

112
      el.removeEventListener('wheel', onWheel, false);
×
113
      el.removeEventListener('touchstart', onTouchStart, false);
×
114
      el.removeEventListener('touchmove', onTouchMove, false);
×
115
    },
116
    [onTouchMove, onTouchStart, onWheel]
117
  );
118

119
  useEffect(() => {
768✔
120
    if (!isEnabled) return;
169!
121

122
    const element = scrollTarget.current;
×
123
    startListening(element);
×
124

125
    return () => {
×
126
      stopListening(element);
×
127
    };
128
  }, [isEnabled, startListening, stopListening]);
129

130
  return (element: HTMLElement | null) => {
768✔
131
    scrollTarget.current = element;
1,536✔
132
  };
133
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc