diff --git a/.gitignore b/.gitignore index f080f0a..054db59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -dist/ - # OSX # .DS_Store @@ -34,4 +32,7 @@ npm-debug.log # yarn # -/.yarn/install-state.gz \ No newline at end of file +/.yarn/install-state.gz + +# Jetbrains +.idea diff --git a/src/AdaptivePopover.tsx b/src/AdaptivePopover.tsx index 562312d..790d933 100644 --- a/src/AdaptivePopover.tsx +++ b/src/AdaptivePopover.tsx @@ -28,7 +28,7 @@ export default class AdaptivePopover extends Component { this.setState( - { defaultDisplayArea: newDisplayArea, displayAreaOffset }, + { + defaultDisplayArea: newDisplayArea, + displayAreaOffset + }, () => resolve(null) ); }); @@ -210,16 +214,39 @@ export default class AdaptivePopover extends Component { const { fromRef }: Partial = this.props; const initialRect = this.state.fromRect || new Rect(0, 0, 0, 0); - const displayAreaOffset = this.state.displayAreaOffset ?? { x: 0, y: 0 }; + const displayAreaOffset = this.state.displayAreaOffset ?? { + x: 0, + y: 0 + }; this.debug('calculateRectFromRef - waiting for ref'); + + // If no fromRef is provided, exit early + if (!fromRef) { + this.debug('calculateRectFromRef - no fromRef provided'); + return; + } + let count = 0; - while (!fromRef?.current) { + // Wait for ref.current to be available, but don't block forever + while (!fromRef.current) { await new Promise(resolve => { setTimeout(resolve, 100); }); // Timeout after 2 seconds - if (count++ > 20) return; + if (count++ > 20) { + this.debug('calculateRectFromRef - timed out waiting for ref.current'); + // If we can't get a ref after waiting, use a default rect in the center of the screen + if (this._isMounted) { + const { + width, + height + } = Dimensions.get('window'); + const defaultRect = new Rect(width / 2, height / 2, 0, 0); + this.setState({ fromRect: defaultRect }); + } + return; + } } const shouldAdjustForAndroidStatusBar = @@ -233,30 +260,80 @@ export default class AdaptivePopover extends Component i === undefined)) { - this.debug('calculateRectFromRef - rect not found, all properties undefined'); - return; - } - rect = new Rect(rect.x + horizontalOffset, rect.y + verticalOffset, rect.width, rect.height); - if (count === 0 && AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)) { - break; - } + try { + do { + try { + rect = await getRectForRef(fromRef); + + // Check if we got a valid rect + if (rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0) { + // This is our default rect from getRectForRef, meaning the ref wasn't ready + this.debug('calculateRectFromRef - got default rect, waiting for valid rect'); + await new Promise(resolve => setTimeout(resolve, 100)); + if (count++ > 20) { + // If we can't get a valid rect after waiting, use a default rect + this.debug('calculateRectFromRef - timed out waiting for valid rect'); + if (this._isMounted) { + const { + width, + height + } = Dimensions.get('window'); + rect = new Rect(width / 2, height / 2, 0, 0); + this.setState({ fromRect: rect }); + } + return; + } + continue; + } - await new Promise(resolve => { - setTimeout(resolve, 100); - }); - // Timeout after 2 seconds - if (count++ > 20) return; + if ([rect.x, rect.y, rect.width, rect.height].every(i => i === undefined)) { + this.debug('calculateRectFromRef - rect not found, all properties undefined'); + return; + } + + rect = new Rect(rect.x + horizontalOffset, rect.y + verticalOffset, rect.width, rect.height); + + if (count === 0 && AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)) { + break; + } - } while (!AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)); + await new Promise(resolve => { + setTimeout(resolve, 100); + }); + // Timeout after 2 seconds + if (count++ > 20) { + this.debug('calculateRectFromRef - timed out waiting for satisfying rect'); + break; + } - this.debug('calculateRectFromRef - calculated Rect', rect); - if (this._isMounted) this.setState({ fromRect: rect }); + } catch (error) { + this.debug(`calculateRectFromRef - error getting rect: ${error}`); + // If we encounter an error, wait a bit and try again + await new Promise(resolve => setTimeout(resolve, 100)); + if (count++ > 20) { + this.debug('calculateRectFromRef - timed out after errors'); + return; + } + } + } while (!rect || !AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)); + + this.debug('calculateRectFromRef - calculated Rect', rect); + if (this._isMounted) this.setState({ fromRect: rect }); + } catch (error) { + this.debug(`calculateRectFromRef - unexpected error: ${error}`); + // If we encounter an unexpected error, use a default rect in the center of the screen + if (this._isMounted) { + const { + width, + height + } = Dimensions.get('window'); + const defaultRect = new Rect(width / 2, height / 2, 0, 0); + this.setState({ fromRect: defaultRect }); + } + } } static hasRetrievedSatisfyingRect = (rect: Rect, initialRect: Rect): boolean => @@ -264,11 +341,20 @@ export default class AdaptivePopover extends Component { return new Promise((resolve, reject) => { if (ref.current) { - ref.current.measureInWindow( - (x: number, y: number, width: number, height: number) => - resolve(new Rect(x, y, width, height)) - ); + try { + ref.current.measureInWindow( + (x: number, y: number, width: number, height: number) => + resolve(new Rect(x, y, width, height)) + ); + } catch (error) { + // If measureInWindow fails, return a default rect + console.warn('getRectForRef - measureInWindow failed:', error); + resolve(new Rect(0, 0, 0, 0)); + } } else { - reject(new Error('getRectForRef - current is not set')); + // Instead of rejecting, resolve with a default rect + console.warn('getRectForRef - current is not set'); + resolve(new Rect(0, 0, 0, 0)); } }); } @@ -27,23 +35,47 @@ export async function waitForChange( // Failsafe so that the interval doesn't run forever let count = 0; let first, second; - do { - first = await getFirst(); - second = await getSecond(); - await new Promise(resolve => { - setTimeout(resolve, 100); - }); - count++; - if (count++ > 20) { - throw new Error('waitForChange - Timed out waiting for change (waited 2 seconds)'); - } - } while (first.equals(second)); + try { + do { + try { + first = await getFirst(); + second = await getSecond(); + } catch (error) { + console.warn('waitForChange - error getting rects:', error); + await new Promise(resolve => { + setTimeout(resolve, 100); + }); + count++; + if (count > 20) { + console.warn('waitForChange - Timed out waiting for valid rects (waited 2 seconds)'); + return; + } + continue; + } + + await new Promise(resolve => { + setTimeout(resolve, 100); + }); + count++; + if (count > 20) { + console.warn('waitForChange - Timed out waiting for change (waited 2 seconds)'); + return; + } + } while (first && second && first.equals(second)); + } catch (error) { + console.warn('waitForChange - unexpected error:', error); + } } export async function waitForNewRect(ref: RefType, initialRect: Rect): Promise { - await waitForChange(() => getRectForRef(ref), () => Promise.resolve(initialRect)); - const rect = await getRectForRef(ref); - return rect; + try { + await waitForChange(() => getRectForRef(ref), () => Promise.resolve(initialRect)); + const rect = await getRectForRef(ref); + return rect; + } catch (error) { + console.warn('waitForNewRect - error:', error); + return initialRect; // Return the initial rect if there's an error + } } export function sizeChanged(a: Size | null, b: Size | null): boolean {