@skyscanner/backpack-web 42.13.0 → 42.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
-
.bpk-scrim-content{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;overflow:auto;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}
|
|
18
|
+
.bpk-scrim-content{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;overflow:auto;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y}
|
|
@@ -4,3 +4,5 @@ export declare const fixBody: () => void;
|
|
|
4
4
|
export declare const unfixBody: () => void;
|
|
5
5
|
export declare const lockScroll: () => void;
|
|
6
6
|
export declare const unlockScroll: () => void;
|
|
7
|
+
export declare const lockTouchAction: () => void;
|
|
8
|
+
export declare const unlockTouchAction: () => void;
|
|
@@ -16,7 +16,19 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
// Module-level state is intentional: the <body> element is a global singleton,
|
|
20
|
+
// so nested scrims must coordinate through a shared counter. Each lock/unlock
|
|
21
|
+
// pair maintains its own depth counter so the "save original value, apply
|
|
22
|
+
// locked value" logic only runs on the 0↔1 transition. Inner nested calls are
|
|
23
|
+
// no-ops that simply bump the counter. This prevents the inner scrim from
|
|
24
|
+
// overwriting the outer scrim's saved "pre-lock" value — see the Nested story
|
|
25
|
+
// in BpkModal.stories.tsx.
|
|
19
26
|
let scrollOffset = 0;
|
|
27
|
+
let savedTouchAction = '';
|
|
28
|
+
let savedOverscrollBehavior = '';
|
|
29
|
+
let fixBodyDepth = 0;
|
|
30
|
+
let lockScrollDepth = 0;
|
|
31
|
+
let lockTouchActionDepth = 0;
|
|
20
32
|
const getWindow = () => typeof window !== 'undefined' ? window : null;
|
|
21
33
|
const getBodyElement = () => typeof document !== 'undefined' && typeof document.body !== 'undefined' ? document.body : null;
|
|
22
34
|
const getScrollBarWidth = () => {
|
|
@@ -41,6 +53,12 @@ const getScrollBarWidth = () => {
|
|
|
41
53
|
return scrollBarWidth === 0 ? '' : `${scrollBarWidth}px`;
|
|
42
54
|
};
|
|
43
55
|
export const storeScroll = () => {
|
|
56
|
+
// Skip while an outer scrim already fixed the body: pageYOffset reads 0 once
|
|
57
|
+
// the body is position:fixed, so capturing it would clobber the outer
|
|
58
|
+
// scroll position and cause restoreScroll to jump back to the top on close.
|
|
59
|
+
if (fixBodyDepth > 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
44
62
|
const window = getWindow();
|
|
45
63
|
if (window) {
|
|
46
64
|
scrollOffset = window.pageYOffset;
|
|
@@ -57,29 +75,81 @@ export const fixBody = () => {
|
|
|
57
75
|
if (!body) {
|
|
58
76
|
return;
|
|
59
77
|
}
|
|
60
|
-
|
|
78
|
+
if (fixBodyDepth === 0) {
|
|
79
|
+
// Set top before position:fixed so the browser doesn't jump to scrollY=0.
|
|
80
|
+
// scrollOffset is captured by storeScroll() immediately before this call.
|
|
81
|
+
body.style.top = `-${scrollOffset}px`;
|
|
82
|
+
body.style.width = '100%';
|
|
83
|
+
body.style.position = 'fixed';
|
|
84
|
+
}
|
|
85
|
+
fixBodyDepth += 1;
|
|
61
86
|
};
|
|
62
87
|
export const unfixBody = () => {
|
|
63
88
|
const body = getBodyElement();
|
|
64
|
-
if (!body) {
|
|
89
|
+
if (!body || fixBodyDepth === 0) {
|
|
65
90
|
return;
|
|
66
91
|
}
|
|
67
|
-
|
|
92
|
+
fixBodyDepth -= 1;
|
|
93
|
+
if (fixBodyDepth === 0) {
|
|
94
|
+
body.style.position = '';
|
|
95
|
+
body.style.top = '';
|
|
96
|
+
body.style.width = '';
|
|
97
|
+
}
|
|
68
98
|
};
|
|
99
|
+
|
|
100
|
+
// Locks background scroll on the body. Safe to call on all platforms.
|
|
101
|
+
// None of these block user touch gestures.
|
|
69
102
|
export const lockScroll = () => {
|
|
70
103
|
const body = getBodyElement();
|
|
71
104
|
if (!body) {
|
|
72
105
|
return;
|
|
73
106
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
107
|
+
if (lockScrollDepth === 0) {
|
|
108
|
+
savedOverscrollBehavior = body.style.overscrollBehavior;
|
|
109
|
+
body.style.overflow = 'hidden';
|
|
110
|
+
body.style.paddingRight = getScrollBarWidth();
|
|
111
|
+
body.style.overscrollBehavior = 'contain';
|
|
112
|
+
}
|
|
113
|
+
lockScrollDepth += 1;
|
|
77
114
|
};
|
|
78
115
|
export const unlockScroll = () => {
|
|
116
|
+
const body = getBodyElement();
|
|
117
|
+
if (!body || lockScrollDepth === 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
lockScrollDepth -= 1;
|
|
121
|
+
if (lockScrollDepth === 0) {
|
|
122
|
+
body.style.overflow = '';
|
|
123
|
+
body.style.paddingRight = '';
|
|
124
|
+
body.style.overscrollBehavior = savedOverscrollBehavior;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Blocks touch gestures on the body via `touch-action: none`. iOS-only.
|
|
129
|
+
//
|
|
130
|
+
// On iOS Safari, `overflow: hidden` alone does not stop touch-scroll or
|
|
131
|
+
// rubber-band overscroll on the body — `touch-action: none` is needed. Do NOT
|
|
132
|
+
// call this on non-iOS platforms: `touch-action` combines with descendants'
|
|
133
|
+
// effective touch-action, so setting `none` on body blocks touch scrolling in
|
|
134
|
+
// any modal content that doesn't explicitly declare its own `touch-action`.
|
|
135
|
+
export const lockTouchAction = () => {
|
|
79
136
|
const body = getBodyElement();
|
|
80
137
|
if (!body) {
|
|
81
138
|
return;
|
|
82
139
|
}
|
|
83
|
-
|
|
84
|
-
|
|
140
|
+
if (lockTouchActionDepth === 0) {
|
|
141
|
+
savedTouchAction = body.style.touchAction;
|
|
142
|
+
body.style.touchAction = 'none';
|
|
143
|
+
}
|
|
144
|
+
lockTouchActionDepth += 1;
|
|
145
|
+
};
|
|
146
|
+
export const unlockTouchAction = () => {
|
|
147
|
+
const body = getBodyElement();
|
|
148
|
+
if (!body || lockTouchActionDepth === 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
lockTouchActionDepth -= 1;
|
|
152
|
+
if (lockTouchActionDepth === 0) {
|
|
153
|
+
body.style.touchAction = savedTouchAction;
|
|
154
|
+
}
|
|
85
155
|
};
|
|
@@ -21,7 +21,7 @@ import { cssModules, isDeviceIpad, isDeviceIphone, wrapDisplayName } from "../..
|
|
|
21
21
|
import BpkScrim from "./BpkScrim";
|
|
22
22
|
import focusScope from "./focusScope";
|
|
23
23
|
import focusStore from "./focusStore";
|
|
24
|
-
import { fixBody, lockScroll, restoreScroll, storeScroll, unfixBody, unlockScroll } from "./scroll-utils";
|
|
24
|
+
import { fixBody, lockScroll, lockTouchAction, restoreScroll, storeScroll, unfixBody, unlockScroll, unlockTouchAction } from "./scroll-utils";
|
|
25
25
|
import STYLES from "./bpk-scrim-content.module.css";
|
|
26
26
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
27
27
|
const getClassName = cssModules(STYLES);
|
|
@@ -41,33 +41,27 @@ const withScrim = WrappedComponent => {
|
|
|
41
41
|
isIphone
|
|
42
42
|
} = this.props;
|
|
43
43
|
const applicationElement = getApplicationElement();
|
|
44
|
-
requestAnimationFrame(() => {
|
|
45
|
-
/**
|
|
46
|
-
* iPhones & iPads need to have a fixed body
|
|
47
|
-
* and scrolling stored to prevent some iOS specific issues occuring
|
|
48
|
-
*
|
|
49
|
-
* Issue description:
|
|
50
|
-
* iOS safari does not prevent scrolling on the underlying content.
|
|
51
|
-
* Without the below fixes this results in users being able to scroll below any modal or dialog that uses withScrim.
|
|
52
|
-
*
|
|
53
|
-
* The fixes can be summaried here: https://markus.oberlehner.net/blog/simple-solution-to-prevent-body-scrolling-on-ios/
|
|
54
|
-
*
|
|
55
|
-
* The most dangerous of the fixes below is the fixBody, this function applies changes to the <body> style.
|
|
56
|
-
* This has the potential to override any custom styles already applied. The assumption here is that no one internally is making these changes to body.
|
|
57
|
-
*
|
|
58
|
-
* There is a corresponding set of functions in the componentWillUnmount block that deals with undoing these changes.
|
|
59
|
-
*/
|
|
60
|
-
if (isIphone || isIpad) {
|
|
61
|
-
storeScroll();
|
|
62
|
-
fixBody();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* lockScroll and the associated unlockScroll is how we control the scroll behaviour of the application when the scrim is active.
|
|
66
|
-
* The desired behaviour is to prevent the user from scrolling content behind the scrim. The above iOS fixes are in place because lockScroll alone does not solve due to iOS specific issues.
|
|
67
|
-
*/
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
|
|
45
|
+
/**
|
|
46
|
+
* iPhones & iPads need to have a fixed body and scrolling stored to prevent some iOS
|
|
47
|
+
* specific issues occurring. iOS Safari does not prevent scrolling on the underlying
|
|
48
|
+
* content — without these fixes users can scroll below a modal or dialog that uses
|
|
49
|
+
* withScrim. See: https://markus.oberlehner.net/blog/simple-solution-to-prevent-body-scrolling-on-ios/
|
|
50
|
+
*
|
|
51
|
+
* The most dangerous of the fixes below is fixBody — it applies changes to the <body>
|
|
52
|
+
* style and has the potential to override any custom styles already applied. The
|
|
53
|
+
* assumption here is that no one internally is making these changes to body.
|
|
54
|
+
*
|
|
55
|
+
* These must run synchronously (not inside requestAnimationFrame) so the body is
|
|
56
|
+
* locked before the first paint, otherwise a visible scroll-jump occurs on open.
|
|
57
|
+
* componentWillUnmount has a corresponding set of calls that undo these changes.
|
|
58
|
+
*/
|
|
59
|
+
if (isIphone || isIpad) {
|
|
60
|
+
storeScroll();
|
|
61
|
+
fixBody();
|
|
62
|
+
lockTouchAction();
|
|
63
|
+
}
|
|
64
|
+
lockScroll();
|
|
71
65
|
if (applicationElement) {
|
|
72
66
|
applicationElement.setAttribute('aria-hidden', 'true');
|
|
73
67
|
}
|
|
@@ -84,8 +78,11 @@ const withScrim = WrappedComponent => {
|
|
|
84
78
|
} = this.props;
|
|
85
79
|
const applicationElement = getApplicationElement();
|
|
86
80
|
if (isIphone || isIpad) {
|
|
87
|
-
|
|
81
|
+
// unfixBody before restoreScroll: restoring scroll while body is still fixed
|
|
82
|
+
// prevents a second visual jump on close.
|
|
88
83
|
unfixBody();
|
|
84
|
+
restoreScroll();
|
|
85
|
+
unlockTouchAction();
|
|
89
86
|
}
|
|
90
87
|
unlockScroll();
|
|
91
88
|
if (applicationElement) {
|