NativeSafeAreaView.web.tsx 3.6KB

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