UNPKG

stream-chat-react-native-core

Version:

The official React Native and Expo components for Stream Chat, a service for building chat applications

84 lines (74 loc) 3.42 kB
import React from 'react'; import { Dimensions, Platform, View } from 'react-native'; import { EdgeInsets } from 'react-native-safe-area-context'; import { isRN86OrGreater } from '../../../utils/react-native-constants'; type MeasuredRect = { x: number; y: number; w: number; h: number }; /** * On Android, `measureInWindow` historically did not account for the top inset, so we added it * ourselves. React Native 0.86 fixed this upstream as part of its edge-to-edge rework and now * returns topInset corrected window coordinates. On >= 0.86 adding it again double counts the * inset and offsets every measured view down by `insets.top`, so we only compensate on Android * running React Native below 0.86. */ const androidTopInsetCompensation = (insets: EdgeInsets): number => Platform.OS === 'android' && !isRN86OrGreater ? insets.top : 0; /** * How many screen lengths away from the origin a measured coordinate may fall before we treat it as * bogus. Real targets (a view currently on screen) always measure well within one screen and the failure * mode we guard against is off by tens of screens, so a generous multiplier cleanly separates the two * with no risk of false positives on legitimate values. */ const SCREEN_BOUND_MULTIPLIER = 2; /** * `measureInWindow` can return wildly out of bounds coordinates on Android when the native window has * been forced edge-to-edge behind React Native's back (i.e a library setting `FLAG_LAYOUT_NO_LIMITS`, * as `react-native-system-navigation-bar` does for a transparent nav bar) while RN's own edge-to-edge * (`edgeToEdgeEnabled`) is off. In that state the window relative measurement is corrupted and would * return a position of the View far offscreen. */ export const isMeasuredRectBogus = (x: number, y: number, w: number, h: number): boolean => { if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w) || !Number.isFinite(h)) { return true; } if (w <= 0 || h <= 0) { return true; } const { width, height } = Dimensions.get('screen'); if (width <= 0 || height <= 0) { // can't reason about bounds, trust the measurement return false; } return ( Math.abs(x) > width * SCREEN_BOUND_MULTIPLIER || Math.abs(y) > height * SCREEN_BOUND_MULTIPLIER ); }; export const measureInWindow = ( node: React.RefObject<View | null>, insets: EdgeInsets, ): Promise<MeasuredRect> => { return new Promise((resolve, reject) => { const handle = node.current; if (!handle) { return reject( new Error('The native handle could not be found while invoking measureInWindow.'), ); } handle.measureInWindow((x, y, w, h) => { if (!isMeasuredRectBogus(x, y, w, h)) { resolve({ h, w, x, y: y + androidTopInsetCompensation(insets) }); return; } // If `measureInWindow` returned an out of bounds rect, fallback to `measure()`, whose // `pageX`/`pageY` are relative to the app root and are the same coordinate space as the window. // They will stays correct when the window frame has been mutated out from under // React Native. if (typeof handle.measure !== 'function') { resolve({ h, w, x, y: y + androidTopInsetCompensation(insets) }); return; } handle.measure((_x, _y, width, height, pageX, pageY) => { resolve({ h: height, w: width, x: pageX, y: pageY }); }); }); }); };