UNPKG

react-native-safe-area-context

Version:

A flexible way to handle safe area, also works on Android and web.

160 lines (157 loc) 5.29 kB
/* eslint-env browser */ import * as React from 'react'; import { View } from 'react-native'; const CSSTransitions = { WebkitTransition: 'webkitTransitionEnd', Transition: 'transitionEnd', MozTransition: 'transitionend', MSTransition: 'msTransitionEnd', OTransition: 'oTransitionEnd' }; export function NativeSafeAreaProvider({ children, style, onInsetsChange }) { const viewRef = React.useRef(null); React.useEffect(() => { // Skip for SSR. if (typeof document === 'undefined') { return; } // On react-native-web the view ref is the underlying DOM element. const providerElement = viewRef.current; // When the provider element cannot be measured or observed, insets and // frame stay based on the window instead of the provider view. const canMeasureProviderElement = typeof ResizeObserver !== 'undefined' && providerElement != null && typeof providerElement.getBoundingClientRect === 'function'; const element = createContextElement(); document.body.appendChild(element); const onEnd = () => { const { paddingTop, paddingBottom, paddingLeft, paddingRight } = window.getComputedStyle(element); const windowInsets = { top: paddingTop ? parseInt(paddingTop, 10) : 0, bottom: paddingBottom ? parseInt(paddingBottom, 10) : 0, left: paddingLeft ? parseInt(paddingLeft, 10) : 0, right: paddingRight ? parseInt(paddingRight, 10) : 0 }; let insets = windowInsets; let frame = { x: 0, y: 0, width: document.documentElement.offsetWidth, height: document.documentElement.offsetHeight }; if (canMeasureProviderElement) { // Match the native implementations: report the provider view's frame // in viewport coordinates and clamp the window insets to the part // overlapping the provider view. const rect = providerElement.getBoundingClientRect(); insets = { top: Math.max(0, windowInsets.top - rect.top), bottom: Math.max(0, windowInsets.bottom - (window.innerHeight - rect.bottom)), left: Math.max(0, windowInsets.left - rect.left), right: Math.max(0, windowInsets.right - (window.innerWidth - rect.right)) }; frame = { x: rect.left, y: rect.top, width: rect.width, height: rect.height }; } // @ts-ignore: missing properties onInsetsChange({ nativeEvent: { insets, frame } }); }; element.addEventListener(getSupportedTransitionEvent(), onEnd); window.addEventListener('resize', onEnd); onEnd(); let resizeObserver = null; if (canMeasureProviderElement) { // Note this only observes size changes. Position-only changes (an // ancestor scrolling or a layout shift that moves the element without // resizing it) are not re-reported until the next resize or env() // change. This matches the native implementations, which are driven // by layout and inset change events, and keeps scrolling free of // per-frame measurement work. resizeObserver = new ResizeObserver(onEnd); resizeObserver.observe(providerElement); } return () => { element.removeEventListener(getSupportedTransitionEvent(), onEnd); window.removeEventListener('resize', onEnd); resizeObserver?.disconnect(); element.remove(); }; }, [onInsetsChange]); return /*#__PURE__*/React.createElement(View, { ref: viewRef, style: style }, children); } let _supportedTransitionEvent = null; function getSupportedTransitionEvent() { 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; } let _supportedEnv = null; function getSupportedEnv() { if (_supportedEnv !== null) { return _supportedEnv; } 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) { return `${getSupportedEnv()}(safe-area-inset-${side})`; } function createContextElement() { 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; } //# sourceMappingURL=NativeSafeAreaProvider.web.js.map