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 ) )
222
323let locks = [ ]
24+ let documentListenerAdded = false
25+ let initialClientY = - 1
426let previousBodyOverflowSetting
527let previousBodyPaddingRight
628
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+
757const setOverflowHidden = options => {
858 // If previousBodyPaddingRight is already set, don't set it again.
959 if ( previousBodyPaddingRight === undefined ) {
@@ -40,6 +90,30 @@ const restoreOverflowSetting = () => {
4090 previousBodyOverflowSetting = undefined
4191 }
4292}
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+ }
43117
44118export const disableBodyScroll = ( targetElement , options ) => {
45119 // targetElement must be provided
@@ -63,7 +137,48 @@ export const disableBodyScroll = (targetElement, options) => {
63137
64138 locks = [ ...locks , lock ]
65139
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 = [ ]
67182}
68183
69184export const enableBodyScroll = targetElement => {
@@ -77,7 +192,15 @@ export const enableBodyScroll = targetElement => {
77192
78193 locks = locks . filter ( lock => lock . targetElement !== targetElement )
79194
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 ) {
81204 restoreOverflowSetting ( )
82205 }
83206}
0 commit comments