|
13 | 13 | import {calculatePosition, PositionResult} from './calculatePosition';
|
14 | 14 | import {DOMAttributes} from '@react-types/shared';
|
15 | 15 | import {Placement, PlacementAxis, PositionProps} from '@react-types/overlays';
|
16 |
| -import {RefObject, useCallback, useRef, useState} from 'react'; |
| 16 | +import {RefObject, useCallback, useEffect, useRef, useState} from 'react'; |
17 | 17 | import {useCloseOnScroll} from './useCloseOnScroll';
|
18 | 18 | import {useLayoutEffect, useResizeObserver} from '@react-aria/utils';
|
19 | 19 | import {useLocale} from '@react-aria/i18n';
|
@@ -124,11 +124,25 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
124 | 124 | arrowSize
|
125 | 125 | ];
|
126 | 126 |
|
| 127 | + // Note, the position freezing breaks if body sizes itself dynamicly with the visual viewport but that might |
| 128 | + // just be a non-realistic use case |
| 129 | + // Upon opening a overlay, record the current visual viewport scale so we can freeze the overlay styles |
| 130 | + let lastScale = useRef(visualViewport?.scale); |
| 131 | + useEffect(() => { |
| 132 | + if (isOpen) { |
| 133 | + lastScale.current = visualViewport?.scale; |
| 134 | + } |
| 135 | + }, [isOpen]); |
| 136 | + |
127 | 137 | let updatePosition = useCallback(() => {
|
128 | 138 | if (shouldUpdatePosition === false || !isOpen || !overlayRef.current || !targetRef.current || !scrollRef.current || !boundaryElement) {
|
129 | 139 | return;
|
130 | 140 | }
|
131 | 141 |
|
| 142 | + if (visualViewport?.scale !== lastScale.current) { |
| 143 | + return; |
| 144 | + } |
| 145 | + |
132 | 146 | let position = calculatePosition({
|
133 | 147 | placement: translateRTL(placement, direction),
|
134 | 148 | overlayNode: overlayRef.current,
|
@@ -183,12 +197,19 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
183 | 197 | updatePosition();
|
184 | 198 | };
|
185 | 199 |
|
186 |
| - visualViewport?.addEventListener('resize', onResize); |
187 |
| - visualViewport?.addEventListener('scroll', onResize); |
| 200 | + // Only reposition the overlay if a scroll event happens immediately as a result of resize (aka the virtual keyboard has appears) |
| 201 | + // We don't want to reposition the overlay if the user has pinch zoomed in and is scrolling the viewport around. |
| 202 | + let onScroll = () => { |
| 203 | + if (isResizing.current) { |
| 204 | + onResize(); |
| 205 | + } |
| 206 | + }; |
188 | 207 |
|
| 208 | + visualViewport?.addEventListener('resize', onResize); |
| 209 | + visualViewport?.addEventListener('scroll', onScroll); |
189 | 210 | return () => {
|
190 | 211 | visualViewport?.removeEventListener('resize', onResize);
|
191 |
| - visualViewport?.removeEventListener('scroll', onResize); |
| 212 | + visualViewport?.removeEventListener('scroll', onScroll); |
192 | 213 | };
|
193 | 214 | }, [updatePosition]);
|
194 | 215 |
|
|
0 commit comments