forked from AppAndFlow/react-native-safe-area-context
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNativeSafeAreaView.web.tsx
123 lines (110 loc) · 3.35 KB
/
NativeSafeAreaView.web.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import * as React from 'react';
import { ViewStyle, View } from 'react-native';
import { InsetChangeNativeCallback } from './SafeArea.types';
interface NativeSafeAreaViewProps {
children?: React.ReactNode;
style: ViewStyle;
onInsetsChange: InsetChangeNativeCallback;
}
enum CSSTransitions {
WebkitTransition = 'webkitTransitionEnd',
Transition = 'transitionEnd',
MozTransition = 'transitionend',
MSTransition = 'msTransitionEnd',
OTransition = 'oTransitionEnd',
}
export default function NativeSafeAreaView({
children,
style,
onInsetsChange,
}: NativeSafeAreaViewProps) {
React.useEffect(() => {
// Skip for SSR.
if (typeof document === 'undefined') {
return;
}
const element = createContextElement();
document.body.appendChild(element);
const onEnd = () => {
const {
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
} = window.getComputedStyle(element);
const insets = {
top: paddingTop ? parseInt(paddingTop, 10) : 0,
bottom: paddingBottom ? parseInt(paddingBottom, 10) : 0,
left: paddingLeft ? parseInt(paddingLeft, 10) : 0,
right: paddingRight ? parseInt(paddingRight, 10) : 0,
};
// @ts-ignore: missing properties
onInsetsChange({ nativeEvent: { insets } });
};
element.addEventListener(getSupportedTransitionEvent(), onEnd);
onEnd();
return () => {
document.body.removeChild(element);
element.removeEventListener(getSupportedTransitionEvent(), onEnd);
};
}, [onInsetsChange]);
return <View style={style}>{children}</View>;
}
let _supportedTransitionEvent: string | null = null;
function getSupportedTransitionEvent(): string {
if (_supportedTransitionEvent !== null) {
return _supportedTransitionEvent;
}
const element = document.createElement('invalidtype');
_supportedTransitionEvent = CSSTransitions.Transition;
for (const key in CSSTransitions) {
if (element.style[key] !== undefined) {
_supportedTransitionEvent = CSSTransitions[key];
break;
}
}
return _supportedTransitionEvent;
}
type CssEnv = 'constant' | 'env';
let _supportedEnv: CssEnv | null = null;
function getSupportedEnv(): CssEnv {
if (_supportedEnv !== null) {
return _supportedEnv;
}
// @ts-ignore: Property 'CSS' does not exist on type 'Window'.ts(2339)
const { CSS } = window;
if (
CSS &&
CSS.supports &&
CSS.supports('top: constant(safe-area-inset-top)')
) {
_supportedEnv = 'constant';
} else {
_supportedEnv = 'env';
}
return _supportedEnv;
}
function getInset(side: string): string {
return `${getSupportedEnv()}(safe-area-inset-${side})`;
}
function createContextElement(): HTMLElement {
const element = document.createElement('div');
const { style } = element;
style.position = 'fixed';
style.left = '0';
style.top = '0';
style.width = '0';
style.height = '0';
style.zIndex = '-1';
style.overflow = 'hidden';
style.visibility = 'hidden';
// Bacon: Anything faster than this and the callback will be invoked too early with the wrong insets
style.transitionDuration = '0.05s';
style.transitionProperty = 'padding';
style.transitionDelay = '0s';
style.paddingTop = getInset('top');
style.paddingBottom = getInset('bottom');
style.paddingLeft = getInset('left');
style.paddingRight = getInset('right');
return element;
}