diff --git a/packages/react-select/src/components/Menu.tsx b/packages/react-select/src/components/Menu.tsx index 602e13938e..0678e8f282 100644 --- a/packages/react-select/src/components/Menu.tsx +++ b/packages/react-select/src/components/Menu.tsx @@ -1,13 +1,13 @@ /** @jsx jsx */ import { createContext, - Component, + Fragment, ReactNode, - RefCallback, - ContextType, - useState, + Ref, useCallback, + useContext, useRef, + useState, } from 'react'; import { jsx } from '@emotion/react'; import { createPortal } from 'react-dom'; @@ -55,10 +55,10 @@ interface PlacementArgs { } export function getMenuPlacement({ - maxHeight, + maxHeight: desiredMaxHeight, menuEl, minHeight, - placement, + placement: desiredPlacement, shouldScroll, isFixedPosition, theme, @@ -67,7 +67,7 @@ export function getMenuPlacement({ const scrollParent = getScrollParent(menuEl!); const defaultState: CalculatedMenuPlacementAndHeight = { placement: 'bottom', - maxHeight, + maxHeight: desiredMaxHeight, }; // something went wrong, return default state @@ -99,12 +99,12 @@ export function getMenuPlacement({ const scrollUp = scrollTop + menuTop - marginTop; const scrollDuration = 160; - switch (placement) { + switch (desiredPlacement) { case 'auto': case 'bottom': // 1: the menu will fit, do nothing if (viewSpaceBelow >= menuHeight) { - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: desiredMaxHeight }; } // 2: the menu will fit, if scrolled @@ -113,7 +113,7 @@ export function getMenuPlacement({ animatedScrollTo(scrollParent, scrollDown, scrollDuration); } - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: desiredMaxHeight }; } // 3: the menu will fit, if constrained @@ -140,15 +140,15 @@ export function getMenuPlacement({ // 4. Forked beviour when there isn't enough space below // AUTO: flip the menu, render above - if (placement === 'auto' || isFixedPosition) { + if (desiredPlacement === 'auto' || isFixedPosition) { // may need to be constrained after flipping - let constrainedHeight = maxHeight; + let constrainedHeight = desiredMaxHeight; const spaceAbove = isFixedPosition ? viewSpaceAbove : scrollSpaceAbove; if (spaceAbove >= minHeight) { constrainedHeight = Math.min( spaceAbove - marginBottom - spacing.controlHeight, - maxHeight + desiredMaxHeight ); } @@ -156,17 +156,17 @@ export function getMenuPlacement({ } // BOTTOM: allow browser to increase scrollable area and immediately set scroll - if (placement === 'bottom') { + if (desiredPlacement === 'bottom') { if (shouldScroll) { scrollTo(scrollParent, scrollDown); } - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: desiredMaxHeight }; } break; case 'top': // 1: the menu will fit, do nothing if (viewSpaceAbove >= menuHeight) { - return { placement: 'top', maxHeight }; + return { placement: 'top', maxHeight: desiredMaxHeight }; } // 2: the menu will fit, if scrolled @@ -175,7 +175,7 @@ export function getMenuPlacement({ animatedScrollTo(scrollParent, scrollUp, scrollDuration); } - return { placement: 'top', maxHeight }; + return { placement: 'top', maxHeight: desiredMaxHeight }; } // 3: the menu will fit, if constrained @@ -183,7 +183,7 @@ export function getMenuPlacement({ (!isFixedPosition && scrollSpaceAbove >= minHeight) || (isFixedPosition && viewSpaceAbove >= minHeight) ) { - let constrainedHeight = maxHeight; + let constrainedHeight = desiredMaxHeight; // we want to provide as much of the menu as possible to the user, // so give them whatever is available below rather than the minHeight. @@ -209,9 +209,9 @@ export function getMenuPlacement({ // 4. not enough space, the browser WILL NOT increase scrollable area when // absolutely positioned element rendered above the viewport (only below). // Flip the menu, render below - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: desiredMaxHeight }; default: - throw new Error(`Invalid placement provided "${placement}".`); + throw new Error(`Invalid placement provided "${desiredPlacement}".`); } return defaultState; @@ -240,7 +240,7 @@ export interface MenuProps< > extends CommonPropsAndClassName, MenuPlacementProps { /** Reference to the internal element, consumed by the MenuPlacer component */ - innerRef: RefCallback; + innerRef: Ref; innerProps: JSX.IntrinsicElements['div']; isLoading: boolean; placement: CoercedMenuPlacement; @@ -254,7 +254,7 @@ interface PlacerProps { } interface ChildrenProps { - ref: RefCallback; + ref: Ref; placerProps: PlacerProps; } @@ -294,41 +294,40 @@ export const menuCSS = < zIndex: 1, }); -const PortalPlacementContext = createContext<{ - getPortalPlacement: - | ((menuState: CalculatedMenuPlacementAndHeight) => void) - | null; -}>({ getPortalPlacement: null }); - -interface MenuState { - placement: CoercedMenuPlacement | null; - maxHeight: number; -} +const PortalPlacementContext = + createContext< + | { + setPortalPlacement: (placement: CoercedMenuPlacement) => void; + } + | undefined + >(undefined); // NOTE: internal only -export class MenuPlacer< +export const MenuPlacer = < Option, IsMulti extends boolean, Group extends GroupBase