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
JavaScript
;
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