1
- // stolen from body-scroll-lock and removed ios part
1
+ // stolen from body-scroll-lock
2
+
3
+ // Older browsers don't support event options, feature detect it.
4
+ let hasPassiveEvents = false
5
+ if ( typeof window !== 'undefined' ) {
6
+ const passiveTestOptions = {
7
+ get passive ( ) {
8
+ hasPassiveEvents = true
9
+ return undefined
10
+ }
11
+ }
12
+ window . addEventListener ( 'testPassive' , null , passiveTestOptions )
13
+ window . removeEventListener ( 'testPassive' , null , passiveTestOptions )
14
+ }
15
+
16
+ const isIosDevice =
17
+ typeof window !== 'undefined' &&
18
+ window . navigator &&
19
+ window . navigator . platform &&
20
+ ( / i P ( a d | h o n e | o d ) / . test ( window . navigator . platform ) ||
21
+ ( window . navigator . platform === 'MacIntel' && window . navigator . maxTouchPoints > 1 ) )
2
22
3
23
let locks = [ ]
24
+ let documentListenerAdded = false
25
+ let initialClientY = - 1
4
26
let previousBodyOverflowSetting
5
27
let previousBodyPaddingRight
6
28
29
+ // returns true if `el` should be allowed to receive touchmove events.
30
+ const allowTouchMove = el =>
31
+ locks . some ( lock => {
32
+ if ( lock . options . allowTouchMove && lock . options . allowTouchMove ( el ) ) {
33
+ return true
34
+ }
35
+
36
+ return false
37
+ } )
38
+
39
+ const preventDefault = rawEvent => {
40
+ const e = rawEvent || window . event
41
+
42
+ // For the case whereby consumers adds a touchmove event listener to document.
43
+ // Recall that we do document.addEventListener('touchmove', preventDefault, { passive: false })
44
+ // in disableBodyScroll - so if we provide this opportunity to allowTouchMove, then
45
+ // the touchmove event on document will break.
46
+ if ( allowTouchMove ( e . target ) ) {
47
+ return true
48
+ }
49
+ // Do not prevent if the event has more than one touch (usually meaning this is a multi touch gesture like pinch to zoom).
50
+ if ( e . touches . length > 1 ) return true
51
+
52
+ if ( e . preventDefault ) e . preventDefault ( )
53
+
54
+ return false
55
+ }
56
+
7
57
const setOverflowHidden = options => {
8
58
// If previousBodyPaddingRight is already set, don't set it again.
9
59
if ( previousBodyPaddingRight === undefined ) {
@@ -40,6 +90,30 @@ const restoreOverflowSetting = () => {
40
90
previousBodyOverflowSetting = undefined
41
91
}
42
92
}
93
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
94
+ const isTargetElementTotallyScrolled = targetElement =>
95
+ targetElement ? targetElement . scrollHeight - targetElement . scrollTop <= targetElement . clientHeight : false
96
+
97
+ const handleScroll = ( event , targetElement ) => {
98
+ const clientY = event . targetTouches [ 0 ] . clientY - initialClientY
99
+
100
+ if ( allowTouchMove ( event . target ) ) {
101
+ return false
102
+ }
103
+
104
+ if ( targetElement && targetElement . scrollTop === 0 && clientY > 0 ) {
105
+ // element is at the top of its scroll.
106
+ return preventDefault ( event )
107
+ }
108
+
109
+ if ( isTargetElementTotallyScrolled ( targetElement ) && clientY < 0 ) {
110
+ // element is at the bottom of its scroll.
111
+ return preventDefault ( event )
112
+ }
113
+
114
+ event . stopPropagation ( )
115
+ return true
116
+ }
43
117
44
118
export const disableBodyScroll = ( targetElement , options ) => {
45
119
// targetElement must be provided
@@ -63,7 +137,48 @@ export const disableBodyScroll = (targetElement, options) => {
63
137
64
138
locks = [ ...locks , lock ]
65
139
66
- setOverflowHidden ( options )
140
+ if ( isIosDevice ) {
141
+ targetElement . ontouchstart = event => {
142
+ if ( event . targetTouches . length === 1 ) {
143
+ // detect single touch.
144
+ initialClientY = event . targetTouches [ 0 ] . clientY
145
+ }
146
+ }
147
+ targetElement . ontouchmove = event => {
148
+ if ( event . targetTouches . length === 1 ) {
149
+ // detect single touch.
150
+ handleScroll ( event , targetElement )
151
+ }
152
+ }
153
+
154
+ if ( ! documentListenerAdded ) {
155
+ document . addEventListener ( 'touchmove' , preventDefault , hasPassiveEvents ? { passive : false } : undefined )
156
+ documentListenerAdded = true
157
+ }
158
+ } else {
159
+ setOverflowHidden ( options )
160
+ }
161
+ }
162
+
163
+ export const clearAllBodyScrollLocks = ( ) => {
164
+ if ( isIosDevice ) {
165
+ // Clear all locks ontouchstart/ontouchmove handlers, and the references.
166
+ locks . forEach ( lock => {
167
+ lock . targetElement . ontouchstart = null
168
+ lock . targetElement . ontouchmove = null
169
+ } )
170
+
171
+ if ( documentListenerAdded ) {
172
+ document . removeEventListener ( 'touchmove' , preventDefault , hasPassiveEvents ? { passive : false } : undefined )
173
+ documentListenerAdded = false
174
+ }
175
+ // Reset initial clientY.
176
+ initialClientY = - 1
177
+ } else {
178
+ restoreOverflowSetting ( )
179
+ }
180
+
181
+ locks = [ ]
67
182
}
68
183
69
184
export const enableBodyScroll = targetElement => {
@@ -77,7 +192,15 @@ export const enableBodyScroll = targetElement => {
77
192
78
193
locks = locks . filter ( lock => lock . targetElement !== targetElement )
79
194
80
- if ( ! locks . length ) {
195
+ if ( isIosDevice ) {
196
+ targetElement . ontouchstart = null
197
+ targetElement . ontouchmove = null
198
+
199
+ if ( documentListenerAdded && locks . length === 0 ) {
200
+ document . removeEventListener ( 'touchmove' , preventDefault , hasPassiveEvents ? { passive : false } : undefined )
201
+ documentListenerAdded = false
202
+ }
203
+ } else if ( ! locks . length ) {
81
204
restoreOverflowSetting ( )
82
205
}
83
206
}
0 commit comments