@@ -21,7 +21,7 @@ interface PositioningParams {
2121 currentPopoverRect : DOMRect ;
2222}
2323
24- enum PositionOption {
24+ export enum PositionOption {
2525 BOTTOM_SPAN_RIGHT = 'bottom-span-right' ,
2626 BOTTOM_SPAN_LEFT = 'bottom-span-left' ,
2727 TOP_SPAN_RIGHT = 'top-span-right' ,
@@ -66,29 +66,32 @@ const positioningUtils = {
6666 } ;
6767 } ,
6868 // Adjusts proposed rect so that the resulting popover is always inside the inspector view bounds.
69- insetAdjustedRect :
70- ( { inspectorViewRect, anchorRect, currentPopoverRect, proposedRect} :
71- { inspectorViewRect : DOMRect , anchorRect : DOMRect , currentPopoverRect : DOMRect , proposedRect : ProposedRect } ) :
72- ProposedRect => {
73- if ( inspectorViewRect . left > proposedRect . left ) {
74- proposedRect . left = inspectorViewRect . left ;
75- }
76-
77- if ( inspectorViewRect . right < proposedRect . left + currentPopoverRect . width ) {
78- proposedRect . left = inspectorViewRect . right - currentPopoverRect . width ;
79- }
80-
81- if ( proposedRect . top + currentPopoverRect . height > inspectorViewRect . bottom ) {
82- proposedRect . top = anchorRect . top - currentPopoverRect . height ;
83- }
84- return proposedRect ;
85- } ,
69+ insetAdjustedRect : ( { inspectorViewRect, currentPopoverRect, proposedRect} :
70+ { inspectorViewRect : DOMRect , currentPopoverRect : DOMRect , proposedRect : ProposedRect } ) :
71+ ProposedRect => {
72+ if ( inspectorViewRect . left > proposedRect . left ) {
73+ proposedRect . left = inspectorViewRect . left ;
74+ }
75+
76+ if ( inspectorViewRect . right < proposedRect . left + currentPopoverRect . width ) {
77+ proposedRect . left = inspectorViewRect . right - currentPopoverRect . width ;
78+ }
79+
80+ if ( proposedRect . top < inspectorViewRect . top ) {
81+ proposedRect . top = inspectorViewRect . top ;
82+ }
83+
84+ if ( proposedRect . top + currentPopoverRect . height > inspectorViewRect . bottom ) {
85+ proposedRect . top = inspectorViewRect . bottom - currentPopoverRect . height ;
86+ }
87+ return proposedRect ;
88+ } ,
8689 isInBounds : ( { inspectorViewRect, currentPopoverRect, proposedRect} :
8790 { inspectorViewRect : DOMRect , currentPopoverRect : DOMRect , proposedRect : ProposedRect } ) : boolean => {
88- return inspectorViewRect . left < proposedRect . left &&
89- proposedRect . left + currentPopoverRect . width < inspectorViewRect . right &&
90- inspectorViewRect . top < proposedRect . top &&
91- proposedRect . top + currentPopoverRect . height < inspectorViewRect . bottom ;
91+ return inspectorViewRect . left <= proposedRect . left &&
92+ proposedRect . left + currentPopoverRect . width <= inspectorViewRect . right &&
93+ inspectorViewRect . top <= proposedRect . top &&
94+ proposedRect . top + currentPopoverRect . height <= inspectorViewRect . bottom ;
9295 } ,
9396 isSameRect : ( rect1 : DOMRect | null , rect2 : DOMRect | null ) : boolean => {
9497 if ( ! rect1 || ! rect2 ) {
@@ -100,7 +103,7 @@ const positioningUtils = {
100103 }
101104} ;
102105
103- const proposedRectForRichTooltip = ( { inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions} : {
106+ export const proposedRectForRichTooltip = ( { inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions} : {
104107 inspectorViewRect : DOMRect ,
105108 anchorRect : DOMRect ,
106109 currentPopoverRect : DOMRect ,
@@ -116,53 +119,74 @@ const proposedRectForRichTooltip = ({inspectorViewRect, anchorRect, currentPopov
116119 ] ) ,
117120 ] ;
118121
119- // Tries the positioning options in the order given by `uniqueOrder`.
120- // If none of them work out, we default to showing the tooltip in the bottom right and adjust
121- // its insets so that the tooltip is inside the inspector view bounds.
122- for ( const positionOption of uniqueOrder ) {
123- let proposedRect ;
122+ const getProposedRectForPositionOption = ( positionOption : PositionOption ) : ProposedRect => {
124123 switch ( positionOption ) {
125124 case PositionOption . BOTTOM_SPAN_RIGHT :
126- proposedRect = positioningUtils . bottomSpanRight ( { anchorRect, currentPopoverRect} ) ;
127- break ;
125+ return positioningUtils . bottomSpanRight ( { anchorRect, currentPopoverRect} ) ;
128126 case PositionOption . BOTTOM_SPAN_LEFT :
129- proposedRect = positioningUtils . bottomSpanLeft ( { anchorRect, currentPopoverRect} ) ;
130- break ;
127+ return positioningUtils . bottomSpanLeft ( { anchorRect, currentPopoverRect} ) ;
131128 case PositionOption . TOP_SPAN_RIGHT :
132- proposedRect = positioningUtils . topSpanRight ( { anchorRect, currentPopoverRect} ) ;
133- break ;
129+ return positioningUtils . topSpanRight ( { anchorRect, currentPopoverRect} ) ;
134130 case PositionOption . TOP_SPAN_LEFT :
135- proposedRect = positioningUtils . topSpanLeft ( { anchorRect, currentPopoverRect} ) ;
131+ return positioningUtils . topSpanLeft ( { anchorRect, currentPopoverRect} ) ;
136132 }
133+ } ;
134+
135+ // Tries the positioning options in the order given by `uniqueOrder`.
136+ for ( const positionOption of uniqueOrder ) {
137+ const proposedRect = getProposedRectForPositionOption ( positionOption ) ;
137138 if ( positioningUtils . isInBounds ( { inspectorViewRect, currentPopoverRect, proposedRect} ) ) {
138139 return proposedRect ;
139140 }
140141 }
141142
142- // If none of the options work above, we position to bottom right
143- // and adjust the insets so that it does not go out of bounds.
144- const proposedRect = positioningUtils . bottomSpanRight ( { anchorRect, currentPopoverRect} ) ;
145- return positioningUtils . insetAdjustedRect ( { anchorRect, currentPopoverRect, inspectorViewRect, proposedRect} ) ;
143+ // If none of the options above work, we decide between top or bottom by which
144+ // option is fewer vertical pixels out of the viewport. We pick left/right
145+ // according to `uniqueOrder`. And finally we adjust the insets so that the
146+ // tooltip is not out of bounds.
147+ const bottomProposed = positioningUtils . bottomSpanRight ( { anchorRect, currentPopoverRect} ) ;
148+ const bottomVerticalOutOfBounds =
149+ Math . max ( 0 , bottomProposed . top + currentPopoverRect . height - inspectorViewRect . bottom ) ;
150+ const topProposed = positioningUtils . topSpanRight ( { anchorRect, currentPopoverRect} ) ;
151+ const topVerticalOutOfBounds = Math . max ( 0 , inspectorViewRect . top - topProposed . top ) ;
152+ const prefersBottom = bottomVerticalOutOfBounds <= topVerticalOutOfBounds ;
153+ const fallbackOption = uniqueOrder . find ( option => {
154+ if ( prefersBottom ) {
155+ return option === PositionOption . BOTTOM_SPAN_LEFT || option === PositionOption . BOTTOM_SPAN_RIGHT ;
156+ }
157+ return option === PositionOption . TOP_SPAN_LEFT || option === PositionOption . TOP_SPAN_RIGHT ;
158+ } ) ??
159+ PositionOption . TOP_SPAN_RIGHT ;
160+ const fallbackRect = getProposedRectForPositionOption ( fallbackOption ) ;
161+ return positioningUtils . insetAdjustedRect ( { currentPopoverRect, inspectorViewRect, proposedRect : fallbackRect } ) ;
146162} ;
147163
148- const proposedRectForSimpleTooltip =
164+ export const proposedRectForSimpleTooltip =
149165 ( { inspectorViewRect, anchorRect, currentPopoverRect} :
150166 { inspectorViewRect : DOMRect , anchorRect : DOMRect , currentPopoverRect : DOMRect } ) : ProposedRect => {
151167 // Default options are bottom centered & top centered.
152168 let proposedRect = positioningUtils . bottomCentered ( { anchorRect, currentPopoverRect} ) ;
153169 if ( positioningUtils . isInBounds ( { inspectorViewRect, currentPopoverRect, proposedRect} ) ) {
154170 return proposedRect ;
155171 }
172+ const bottomVerticalOutOfBoundsAmount =
173+ Math . max ( 0 , proposedRect . top + currentPopoverRect . height - inspectorViewRect . bottom ) ;
156174
157175 proposedRect = positioningUtils . topCentered ( { anchorRect, currentPopoverRect} ) ;
158176 if ( positioningUtils . isInBounds ( { inspectorViewRect, currentPopoverRect, proposedRect} ) ) {
159177 return proposedRect ;
160178 }
161-
162- // The default options did not work out, so position it to bottom center
163- // and adjust the insets to make sure that it does not go out of bounds.
164- proposedRect = positioningUtils . bottomCentered ( { anchorRect, currentPopoverRect} ) ;
165- return positioningUtils . insetAdjustedRect ( { anchorRect, currentPopoverRect, inspectorViewRect, proposedRect} ) ;
179+ const topVerticalOutOfBoundsAmount = Math . max ( 0 , inspectorViewRect . top - proposedRect . top ) ;
180+
181+ // The default options did not work out, so compare which option is fewer
182+ // pixels out of the viewport vertically. Pick the better option and
183+ // adjust the insets to make sure that the tooltip is not out of bounds.
184+ if ( bottomVerticalOutOfBoundsAmount <= topVerticalOutOfBoundsAmount ) {
185+ proposedRect = positioningUtils . bottomCentered ( { anchorRect, currentPopoverRect} ) ;
186+ } else {
187+ proposedRect = positioningUtils . topCentered ( { anchorRect, currentPopoverRect} ) ;
188+ }
189+ return positioningUtils . insetAdjustedRect ( { currentPopoverRect, inspectorViewRect, proposedRect} ) ;
166190 } ;
167191
168192export type TooltipVariant = 'simple' | 'rich' ;
0 commit comments