1
+ import { disposables } from '../../utils/disposables'
1
2
import { isIOS } from '../../utils/platform'
2
3
import { ScrollLockStep } from './overflow-store'
3
4
@@ -24,74 +25,91 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
24
25
. some ( ( container ) => container . contains ( el ) )
25
26
}
26
27
27
- d . style ( doc . body , 'marginTop' , `-${ scrollPosition } px` )
28
- window . scrollTo ( 0 , 0 )
28
+ d . microTask ( ( ) => {
29
+ // We need to be able to offset the body with the current scroll position. However, if you
30
+ // have `scroll-behavior: smooth` set, then changing the scrollTop in any way shape or form
31
+ // will trigger a "smooth" scroll and the new position would be incorrect.
32
+ //
33
+ // This is why we are forcing the `scroll-behaviour: auto` here, and then restoring it later.
34
+ // We have to be a bit careful, because removing `scroll-behavior: auto` back to
35
+ // `scroll-behavior: smooth` can start triggering smooth scrolling. Delaying this by a
36
+ // microTask will guarantee that everything is done such that both enter/exit of the Dialog is
37
+ // not using smooth scrolling.
38
+ if ( window . getComputedStyle ( doc . documentElement ) . scrollBehavior !== 'auto' ) {
39
+ let _d = disposables ( )
40
+ _d . style ( doc . documentElement , 'scroll-behavior' , 'auto' )
41
+ d . add ( ( ) => d . microTask ( ( ) => _d . dispose ( ) ) )
42
+ }
29
43
30
- // Relatively hacky, but if you click a link like `<a href="#foo">` in the Dialog, and there
31
- // exists an element on the page (outside of the Dialog) with that id, then the browser will
32
- // scroll to that position. However, this is not the case if the element we want to scroll to
33
- // is higher and the browser needs to scroll up, but it doesn't do that.
34
- //
35
- // Let's try and capture that element and store it, so that we can later scroll to it once the
36
- // Dialog closes.
37
- let scrollToElement : HTMLElement | null = null
38
- d . addEventListener (
39
- doc ,
40
- 'click' ,
41
- ( e ) => {
42
- if ( ! ( e . target instanceof HTMLElement ) ) {
43
- return
44
- }
44
+ d . style ( doc . body , 'marginTop' , `-${ scrollPosition } px` )
45
+ window . scrollTo ( 0 , 0 )
45
46
46
- try {
47
- let anchor = e . target . closest ( 'a' )
48
- if ( ! anchor ) return
49
- let { hash } = new URL ( anchor . href )
50
- let el = doc . querySelector ( hash )
51
- if ( el && ! inAllowedContainer ( el as HTMLElement ) ) {
52
- scrollToElement = el as HTMLElement
47
+ // Relatively hacky, but if you click a link like `<a href="#foo">` in the Dialog, and there
48
+ // exists an element on the page (outside of the Dialog) with that id, then the browser will
49
+ // scroll to that position. However, this is not the case if the element we want to scroll to
50
+ // is higher and the browser needs to scroll up, but it doesn't do that.
51
+ //
52
+ // Let's try and capture that element and store it, so that we can later scroll to it once the
53
+ // Dialog closes.
54
+ let scrollToElement : HTMLElement | null = null
55
+ d . addEventListener (
56
+ doc ,
57
+ 'click' ,
58
+ ( e ) => {
59
+ if ( ! ( e . target instanceof HTMLElement ) ) {
60
+ return
53
61
}
54
- } catch ( err ) { }
55
- } ,
56
- true
57
- )
58
62
59
- d . addEventListener (
60
- doc ,
61
- 'touchmove' ,
62
- ( e ) => {
63
- // Check if we are scrolling inside any of the allowed containers, if not let's cancel the event!
64
- if ( e . target instanceof HTMLElement && ! inAllowedContainer ( e . target as HTMLElement ) ) {
65
- e . preventDefault ( )
66
- }
67
- } ,
68
- { passive : false }
69
- )
63
+ try {
64
+ let anchor = e . target . closest ( 'a' )
65
+ if ( ! anchor ) return
66
+ let { hash } = new URL ( anchor . href )
67
+ let el = doc . querySelector ( hash )
68
+ if ( el && ! inAllowedContainer ( el as HTMLElement ) ) {
69
+ scrollToElement = el as HTMLElement
70
+ }
71
+ } catch ( err ) { }
72
+ } ,
73
+ true
74
+ )
70
75
71
- // Restore scroll position
72
- d . add ( ( ) => {
73
- // Before opening the Dialog, we capture the current pageYOffset, and offset the page with
74
- // this value so that we can also scroll to `(0, 0)`.
75
- //
76
- // If we want to restore a few things can happen:
77
- //
78
- // 1. The window.pageYOffset is still at 0, this means nothing happened, and we can safely
79
- // restore to the captured value earlier.
80
- // 2. The window.pageYOffset is **not** at 0. This means that something happened (e.g.: a
81
- // link was scrolled into view in the background). Ideally we want to restore to this _new_
82
- // position. To do this, we can take the new value into account with the captured value from
83
- // before.
84
- //
85
- // (Since the value of window.pageYOffset is 0 in the first case, we should be able to
86
- // always sum these values)
87
- window . scrollTo ( 0 , window . pageYOffset + scrollPosition )
76
+ d . addEventListener (
77
+ doc ,
78
+ 'touchmove' ,
79
+ ( e ) => {
80
+ // Check if we are scrolling inside any of the allowed containers, if not let's cancel the event!
81
+ if ( e . target instanceof HTMLElement && ! inAllowedContainer ( e . target as HTMLElement ) ) {
82
+ e . preventDefault ( )
83
+ }
84
+ } ,
85
+ { passive : false }
86
+ )
88
87
89
- // If we captured an element that should be scrolled to, then we can try to do that if the
90
- // element is still connected (aka, still in the DOM).
91
- if ( scrollToElement && scrollToElement . isConnected ) {
92
- scrollToElement . scrollIntoView ( { block : 'nearest' } )
93
- scrollToElement = null
94
- }
88
+ // Restore scroll position
89
+ d . add ( ( ) => {
90
+ // Before opening the Dialog, we capture the current pageYOffset, and offset the page with
91
+ // this value so that we can also scroll to `(0, 0)`.
92
+ //
93
+ // If we want to restore a few things can happen:
94
+ //
95
+ // 1. The window.pageYOffset is still at 0, this means nothing happened, and we can safely
96
+ // restore to the captured value earlier.
97
+ // 2. The window.pageYOffset is **not** at 0. This means that something happened (e.g.: a
98
+ // link was scrolled into view in the background). Ideally we want to restore to this _new_
99
+ // position. To do this, we can take the new value into account with the captured value from
100
+ // before.
101
+ //
102
+ // (Since the value of window.pageYOffset is 0 in the first case, we should be able to
103
+ // always sum these values)
104
+ window . scrollTo ( 0 , window . pageYOffset + scrollPosition )
105
+
106
+ // If we captured an element that should be scrolled to, then we can try to do that if the
107
+ // element is still connected (aka, still in the DOM).
108
+ if ( scrollToElement && scrollToElement . isConnected ) {
109
+ scrollToElement . scrollIntoView ( { block : 'nearest' } )
110
+ scrollToElement = null
111
+ }
112
+ } )
95
113
} )
96
114
} ,
97
115
}
0 commit comments