@@ -19,7 +19,6 @@ import Icon from 'components/Icon';
1919import './Flyout.scss' ;
2020import { Swipeable } from 'react-swipeable' ;
2121import getAppRect from 'helpers/getAppRect' ;
22- import debounce from 'lodash/debounce' ;
2322
2423const Flyout = ( ) => {
2524 const { t } = useTranslation ( ) ;
@@ -68,64 +67,73 @@ const Flyout = () => {
6867
6968 useLayoutEffect ( ( ) => {
7069 const tempRefElement = getElementDOMRef ( toggleElement ) ;
71- const appRect = getAppRect ( ) ;
72- const maxHeightValue = appRect . height - horizontalHeadersUsedHeight ;
73- setMaxHeightValue ( maxHeightValue ) ;
7470
75- // Check if the element is in the dom or invisible
71+ // Check if the element is in the DOM or invisible
7672 if ( tempRefElement && tempRefElement . offsetParent === null ) {
7773 return ;
7874 }
7975
80- const calculateAndSetPosition = ( ) => {
81- const tempRefElement = getElementDOMRef ( toggleElement ) ;
82- const appRect = getAppRect ( ) ;
83- const correctedPosition = { x : position . x , y : position . y } ;
84- // Check if toggleElement is not null
85- if ( toggleElement && tempRefElement ) {
76+ const calculateAndMaybeSetPosition = ( ) => {
77+ const refEl = getElementDOMRef ( toggleElement ) ;
78+ const app = getAppRect ( ) ;
79+ // Keep max height in sync with the exact app rect used for positioning
80+ setMaxHeightValue ( app . height - horizontalHeadersUsedHeight ) ;
81+ const next = { x : position . x , y : position . y } ;
82+
83+ if ( toggleElement && refEl ) {
8684 const { x, y } = getFlyoutPositionOnElement ( toggleElement , flyoutRef ) ;
87- correctedPosition . x = x ;
88- correctedPosition . y = y ;
85+ next . x = x ;
86+ next . y = y ;
8987 }
88+
9089 const flyoutRect = flyoutRef . current ?. getBoundingClientRect ( ) ;
91- if ( flyoutRect ) {
90+ if ( flyoutRect && app ) {
9291 const PADDING = 5 ;
93- const widthOverflow = correctedPosition . x + flyoutRect . width + PADDING - appRect . right ;
94- const heightOverflow = correctedPosition . y + flyoutRect . height + PADDING - appRect . bottom ;
92+ const widthOverflow = next . x + flyoutRect . width + PADDING - app . right ;
93+ const heightOverflow = next . y + flyoutRect . height + PADDING - app . bottom ;
9594 if ( widthOverflow > 0 ) {
96- correctedPosition . x -= widthOverflow ;
95+ next . x -= widthOverflow ;
9796 }
9897 if ( heightOverflow > 0 ) {
99- correctedPosition . y -= heightOverflow ;
98+ next . y -= heightOverflow ;
10099 }
101- if ( correctedPosition . x < PADDING ) {
102- correctedPosition . x = PADDING ;
100+ if ( next . x < PADDING ) {
101+ next . x = PADDING ;
103102 }
104- if ( correctedPosition . y < PADDING ) {
105- correctedPosition . y = PADDING ;
103+ if ( next . y < PADDING ) {
104+ next . y = PADDING ;
106105 }
107106 }
108- setCorrectedPosition ( correctedPosition ) ;
107+
108+ setCorrectedPosition ( ( prev ) => {
109+ if ( ! prev || prev . x !== next . x || prev . y !== next . y ) {
110+ return next ;
111+ }
112+ return prev ;
113+ } ) ;
109114 } ;
110115
111- // Wait for flyout to render all items before calculating position
116+ // Run once now and once on the next frame to catch late layout
112117 if ( flyoutRef . current ) {
113- let observer ;
114- const disconnect = debounce (
115- ( ) => observer . disconnect ( ) ,
116- 100 , { leading : false , trailing : true } ) ;
117- const setPosition = ( ) => {
118- calculateAndSetPosition ( ) ;
119- disconnect ( ) ;
120- } ;
121- // Set position at multiple stages to minimize flickering
122- observer = new MutationObserver ( setPosition ) ;
123- observer . observe ( flyoutRef . current , { attributes : true , childList : true , subtree : true } ) ;
124- setPosition ( ) ;
125- requestAnimationFrame ( setPosition ) ;
126- return ( ) => observer . disconnect ( ) ;
118+ calculateAndMaybeSetPosition ( ) ;
119+ requestAnimationFrame ( calculateAndMaybeSetPosition ) ;
127120 }
128- } , [ activeItem , position , items , inputValue ] ) ;
121+
122+ let resizeObserver ;
123+
124+ if ( typeof ResizeObserver !== 'undefined' && flyoutRef . current ) {
125+ resizeObserver = new ResizeObserver ( ( ) => {
126+ calculateAndMaybeSetPosition ( ) ;
127+ } ) ;
128+ resizeObserver . observe ( flyoutRef . current ) ;
129+ }
130+
131+ return ( ) => {
132+ if ( resizeObserver ) {
133+ resizeObserver . disconnect ( ) ;
134+ }
135+ } ;
136+ } , [ activePath , position , items , inputValue , isFlyoutOpen ] ) ;
129137
130138 useLayoutEffect ( ( ) => {
131139 const appRect = getAppRect ( ) ;
0 commit comments