Evan Bacon 5 years ago
parent
commit
161695fe52
8 changed files with 990 additions and 62 deletions
  1. 13
    2
      example/app.json
  2. 12
    2
      package.json
  3. 3
    0
      src/NativeSafeAreaView.tsx
  4. 112
    0
      src/NativeSafeAreaView.web.tsx
  5. 12
    0
      src/SafeArea.types.ts
  6. 8
    20
      src/index.tsx
  7. 1
    1
      tsconfig.json
  8. 829
    37
      yarn.lock

+ 13
- 2
example/app.json View File

@@ -1,4 +1,15 @@
1 1
 {
2 2
   "name": "SafeAreaViewExample",
3
-  "displayName": "SafeAreaViewExample"
4
-}
3
+  "displayName": "SafeAreaViewExample",
4
+  "expo": {
5
+    "entryPoint": "node_modules/expo/AppEntry",
6
+    "name": "react-native-safe-area-context",
7
+    "slug": "react-native-safe-area-context",
8
+    "version": "0.1.0",
9
+    "platforms": ["android", "ios", "web"],
10
+    "web": {
11
+      "display": "fullscreen",
12
+      "barStyle": "black-translucent"
13
+    }
14
+  }
15
+}

+ 12
- 2
package.json View File

@@ -1,8 +1,10 @@
1 1
 {
2 2
   "name": "react-native-safe-area-context",
3 3
   "version": "0.1.0",
4
-  "description": "A flexible way to handle safe area, also works on Android.",
5
-  "main": "src/index.tsx",
4
+  "description": "A flexible way to handle safe area, also works on Android and web.",
5
+  "main": "src/index",
6
+  "module": "src/index",
7
+  "sideEffects": false,
6 8
   "files": [
7 9
     "/android",
8 10
     "!/android/build",
@@ -11,6 +13,9 @@
11 13
     "/*.podspec"
12 14
   ],
13 15
   "author": "Janic Duplessis <janicduplessis@gmail.com>",
16
+  "contributors": [
17
+    "Evan Bacon <bacon@expo.io> (https://github.com/evanbacon)"
18
+  ],
14 19
   "homepage": "https://github.com/th3rdwave/react-native-safe-area-context#readme",
15 20
   "license": "MIT",
16 21
   "scripts": {
@@ -23,6 +28,8 @@
23 28
   "keywords": [
24 29
     "react-native",
25 30
     "react native",
31
+    "react-native-web",
32
+    "expo-web",
26 33
     "safe area",
27 34
     "view"
28 35
   ],
@@ -38,10 +45,13 @@
38 45
     "eslint": "5.16.0",
39 46
     "eslint-config-prettier": "^4.2.0",
40 47
     "eslint-plugin-prettier": "3.0.1",
48
+    "expo": "^34.0.4",
41 49
     "metro-react-native-babel-preset": "^0.55.0",
42 50
     "prettier": "^1.18.2",
43 51
     "react": "^16.8.3",
52
+    "react-dom": "^16.9.0",
44 53
     "react-native": "^0.60.5",
54
+    "react-native-web": "^0.11.7",
45 55
     "typescript": "^3.5.3"
46 56
   },
47 57
   "repository": {

+ 3
- 0
src/NativeSafeAreaView.tsx View File

@@ -0,0 +1,3 @@
1
+import { requireNativeComponent } from 'react-native';
2
+
3
+export default requireNativeComponent('RNCSafeAreaView');

+ 112
- 0
src/NativeSafeAreaView.web.tsx View File

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

+ 12
- 0
src/SafeArea.types.ts View File

@@ -0,0 +1,12 @@
1
+import { NativeSyntheticEvent } from 'react-native';
2
+
3
+export interface EdgeInsets {
4
+  top: number;
5
+  right: number;
6
+  bottom: number;
7
+  left: number;
8
+}
9
+
10
+export type InsetChangedEvent = NativeSyntheticEvent<{ insets: EdgeInsets }>;
11
+
12
+export type InsetChangeNativeCallback = (event: InsetChangedEvent) => void;

+ 8
- 20
src/index.tsx View File

@@ -1,18 +1,7 @@
1 1
 import * as React from 'react';
2
-import {
3
-  requireNativeComponent,
4
-  NativeSyntheticEvent,
5
-  StyleSheet,
6
-} from 'react-native';
7
-
8
-const NativeSafeAreaView = requireNativeComponent('RNCSafeAreaView');
9
-
10
-export interface EdgeInsets {
11
-  top: number;
12
-  right: number;
13
-  bottom: number;
14
-  left: number;
15
-}
2
+import { StyleSheet } from 'react-native';
3
+import { EdgeInsets, InsetChangedEvent } from './SafeArea.types';
4
+import NativeSafeAreaView from './NativeSafeAreaView';
16 5
 
17 6
 const SafeAreaContext = React.createContext<EdgeInsets | null>(null);
18 7
 
@@ -22,12 +11,9 @@ export interface SafeAreaViewProps {
22 11
 
23 12
 export function SafeAreaProvider({ children }: SafeAreaViewProps) {
24 13
   const [insets, setInsets] = React.useState<EdgeInsets | null>(null);
25
-  const onInsetsChange = React.useCallback(
26
-    (event: NativeSyntheticEvent<{ insets: EdgeInsets }>) => {
27
-      setInsets(event.nativeEvent.insets);
28
-    },
29
-    [],
30
-  );
14
+  const onInsetsChange = React.useCallback((event: InsetChangedEvent) => {
15
+    setInsets(event.nativeEvent.insets);
16
+  }, []);
31 17
 
32 18
   return (
33 19
     <NativeSafeAreaView style={styles.fill} onInsetsChange={onInsetsChange}>
@@ -55,3 +41,5 @@ export function useSafeArea(): EdgeInsets {
55 41
   }
56 42
   return safeArea;
57 43
 }
44
+
45
+export { EdgeInsets };

+ 1
- 1
tsconfig.json View File

@@ -10,7 +10,7 @@
10 10
     "module": "commonjs",
11 11
     "strict": true,
12 12
     "moduleResolution": "node",
13
-    "lib": ["es2015", "es2016", "esnext"],
13
+    "lib": ["dom", "es2015", "es2016", "esnext"],
14 14
     "jsx": "react-native"
15 15
   },
16 16
   "exclude": ["node_modules"]

+ 829
- 37
yarn.lock
File diff suppressed because it is too large
View File