NativeSafeAreaView.web.tsx 2.9KB

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