UNPKG

react-native-safe-area-context

Version:

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

167 lines (163 loc) 6.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NativeSafeAreaProvider = NativeSafeAreaProvider; var React = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /* eslint-env browser */ const CSSTransitions = { WebkitTransition: 'webkitTransitionEnd', Transition: 'transitionEnd', MozTransition: 'transitionend', MSTransition: 'msTransitionEnd', OTransition: 'oTransitionEnd' }; 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(_reactNative.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