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

JedWatson / react-select / 516755d9-6918-43db-b43c-030ffccac710

26 Oct 2024 03:36AM CUT coverage: 75.844%. Remained the same
516755d9-6918-43db-b43c-030ffccac710

push

circleci

web-flow
Fix for calling non-cancellable scroll events (#5771)

658 of 1052 branches covered (62.55%)

0 of 1 new or added line in 1 file covered. (0.0%)

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

20.31
/packages/react-select/src/internal/useScrollLock.ts
1
import { useCallback, useEffect, useRef } from 'react';
2

3
const STYLE_KEYS = [
5✔
4
  'boxSizing',
5
  'height',
6
  'overflow',
7
  'paddingRight',
8
  'position',
9
] as const;
10

11
const LOCK_STYLES = {
5✔
12
  boxSizing: 'border-box', // account for possible declaration `width: 100%;` on body
13
  overflow: 'hidden',
14
  position: 'relative',
15
  height: '100%',
16
};
17

18
function preventTouchMove(e: TouchEvent) {
NEW
19
  if (e.cancelable) e.preventDefault();
×
20
}
21

22
function allowTouchMove(e: TouchEvent) {
23
  e.stopPropagation();
×
24
}
25

26
function preventInertiaScroll(this: HTMLElement) {
27
  const top = this.scrollTop;
×
28
  const totalScroll = this.scrollHeight;
×
29
  const currentScroll = top + this.offsetHeight;
×
30

31
  if (top === 0) {
×
32
    this.scrollTop = 1;
×
33
  } else if (currentScroll === totalScroll) {
×
34
    this.scrollTop = top - 1;
×
35
  }
36
}
37

38
// `ontouchstart` check works on most browsers
39
// `maxTouchPoints` works on IE10/11 and Surface
40
function isTouchDevice() {
41
  return 'ontouchstart' in window || navigator.maxTouchPoints;
×
42
}
43

44
const canUseDOM = !!(
5✔
45
  typeof window !== 'undefined' &&
15✔
46
  window.document &&
47
  window.document.createElement
48
);
49

50
let activeScrollLocks = 0;
5✔
51

52
interface Options {
53
  readonly isEnabled: boolean;
54
  readonly accountForScrollbars?: boolean;
55
}
56

57
const listenerOptions = {
5✔
58
  capture: false,
59
  passive: false,
60
};
61

62
export default function useScrollLock({
63
  isEnabled,
64
  accountForScrollbars = true,
768✔
65
}: Options) {
66
  const originalStyles = useRef<{ [key: string]: string }>({});
768✔
67
  const scrollTarget = useRef<HTMLElement | null>(null);
768✔
68

69
  const addScrollLock = useCallback(
768✔
70
    (touchScrollTarget: HTMLElement | null) => {
71
      if (!canUseDOM) return;
×
72

73
      const target = document.body;
×
74
      const targetStyle = target && target.style;
×
75

76
      if (accountForScrollbars) {
×
77
        // store any styles already applied to the body
78
        STYLE_KEYS.forEach((key) => {
×
79
          const val = targetStyle && targetStyle[key];
×
80
          originalStyles.current[key] = val;
×
81
        });
82
      }
83

84
      // apply the lock styles and padding if this is the first scroll lock
85
      if (accountForScrollbars && activeScrollLocks < 1) {
×
86
        const currentPadding =
87
          parseInt(originalStyles.current.paddingRight, 10) || 0;
×
88
        const clientWidth = document.body ? document.body.clientWidth : 0;
×
89
        const adjustedPadding =
90
          window.innerWidth - clientWidth + currentPadding || 0;
×
91

92
        Object.keys(LOCK_STYLES).forEach((key) => {
×
93
          const val = LOCK_STYLES[key as keyof typeof LOCK_STYLES];
×
94
          if (targetStyle) {
×
95
            targetStyle[key as keyof typeof LOCK_STYLES] = val;
×
96
          }
97
        });
98

99
        if (targetStyle) {
×
100
          targetStyle.paddingRight = `${adjustedPadding}px`;
×
101
        }
102
      }
103

104
      // account for touch devices
105
      if (target && isTouchDevice()) {
×
106
        // Mobile Safari ignores { overflow: hidden } declaration on the body.
107
        target.addEventListener('touchmove', preventTouchMove, listenerOptions);
×
108

109
        // Allow scroll on provided target
110
        if (touchScrollTarget) {
×
111
          touchScrollTarget.addEventListener(
×
112
            'touchstart',
113
            preventInertiaScroll,
114
            listenerOptions
115
          );
116
          touchScrollTarget.addEventListener(
×
117
            'touchmove',
118
            allowTouchMove,
119
            listenerOptions
120
          );
121
        }
122
      }
123

124
      // increment active scroll locks
125
      activeScrollLocks += 1;
×
126
    },
127
    [accountForScrollbars]
128
  );
129

130
  const removeScrollLock = useCallback(
768✔
131
    (touchScrollTarget: HTMLElement | null) => {
132
      if (!canUseDOM) return;
×
133

134
      const target = document.body;
×
135
      const targetStyle = target && target.style;
×
136

137
      // safely decrement active scroll locks
138
      activeScrollLocks = Math.max(activeScrollLocks - 1, 0);
×
139

140
      // reapply original body styles, if any
141
      if (accountForScrollbars && activeScrollLocks < 1) {
×
142
        STYLE_KEYS.forEach((key) => {
×
143
          const val = originalStyles.current[key];
×
144
          if (targetStyle) {
×
145
            targetStyle[key] = val;
×
146
          }
147
        });
148
      }
149

150
      // remove touch listeners
151
      if (target && isTouchDevice()) {
×
152
        target.removeEventListener(
×
153
          'touchmove',
154
          preventTouchMove,
155
          listenerOptions
156
        );
157

158
        if (touchScrollTarget) {
×
159
          touchScrollTarget.removeEventListener(
×
160
            'touchstart',
161
            preventInertiaScroll,
162
            listenerOptions
163
          );
164
          touchScrollTarget.removeEventListener(
×
165
            'touchmove',
166
            allowTouchMove,
167
            listenerOptions
168
          );
169
        }
170
      }
171
    },
172
    [accountForScrollbars]
173
  );
174

175
  useEffect(() => {
768✔
176
    if (!isEnabled) return;
169!
177

178
    const element = scrollTarget.current;
×
179
    addScrollLock(element);
×
180

181
    return () => {
×
182
      removeScrollLock(element);
×
183
    };
184
  }, [isEnabled, addScrollLock, removeScrollLock]);
185

186
  return (element: HTMLElement | null) => {
768✔
187
    scrollTarget.current = element;
1,536✔
188
  };
189
}
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