Skip to content

Commit 0efd2d4

Browse files
authored
fix: overlay positioning for v3 submenus (aka non viewport containers) (#9343)
* fix: v3 submenu positioning in the docs * update comment for clarity * Revert "fix: meu unavailable items (#9583)" This reverts commit bdec15c. * Revert "fix: v3 menus alternative approach (#9345)" This reverts commit 3ad9c26. * remove menu copy of useOverlayPosition
1 parent 6e9ba6e commit 0efd2d4

8 files changed

Lines changed: 29 additions & 1380 deletions

File tree

packages/@react-aria/overlays/src/calculatePosition.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ function getMaxHeight(
309309
top: Math.max(boundaryDimensions.top + boundaryToContainerTransformOffset, (visualViewport?.offsetTop ?? boundaryDimensions.top) + boundaryToContainerTransformOffset),
310310
bottom: Math.min((boundaryDimensions.top + boundaryDimensions.height + boundaryToContainerTransformOffset), (visualViewport?.offsetTop ?? 0) + (visualViewport?.height ?? 0))
311311
};
312-
313312
let maxHeight = heightGrowthDirection !== 'top' ?
314313
// We want the distance between the top of the overlay to the bottom of the boundary
315314
Math.max(0,
@@ -558,10 +557,33 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
558557
// Otherwise this returns the height/width of a arbitrary boundary element, and its top/left with respect to the viewport (NOTE THIS MEANS IT DOESNT INCLUDE SCROLL)
559558
let boundaryDimensions = getContainerDimensions(boundaryElement, visualViewport);
560559
let containerDimensions = getContainerDimensions(container, visualViewport);
561-
// If the container is the HTML element wrapping the body element, the retrieved scrollTop/scrollLeft will be equal to the
562-
// body element's scroll. Set the container's scroll values to 0 since the overlay's edge position value in getDelta don't then need to be further offset
563-
// by the container scroll since they are essentially the same containing element and thus in the same coordinate system
564-
let containerOffsetWithBoundary: Offset = getPosition(boundaryElement, container, false);
560+
561+
// There are several difference cases of how to calculate the containerOffsetWithBoundary:
562+
// - boundaryElement is body or HTML and the container is an arbitrary element in the boundary (aka submenu with parent menu as container in v3)
563+
// - boundaryElement and container are both body or HTML element (aka standard popover case)
564+
// - boundaryElement is customized by the user. Container can also be arbitrary (either body/HTML or some other element)
565+
// containerOffsetWithBoundary should always return a value that is the boundary's coordinate offset with respect to the container coord system (container is 0, 0)
566+
let containerOffsetWithBoundary: Offset;
567+
if ((boundaryElement.tagName === 'BODY' || boundaryElement.tagName === 'HTML') && !isViewportContainer) {
568+
// Use getRect instead of getOffset because boundaryDimensions for BODY/HTML is in viewport coordinate space,
569+
// not document coordinate space
570+
let containerRect = getRect(container, false);
571+
// the offset should be negative because if container is at viewport position x,y, then viewport top (aka 0)
572+
// is at position -x,y in container-relative coordinates
573+
containerOffsetWithBoundary = {
574+
top: -(containerRect.top - boundaryDimensions.top),
575+
left: -(containerRect.left - boundaryDimensions.left),
576+
width: 0,
577+
height: 0
578+
};
579+
} else if ((boundaryElement.tagName === 'BODY' || boundaryElement.tagName === 'HTML') && isViewportContainer) {
580+
// both are the same viewport container, no offset needed
581+
containerOffsetWithBoundary = {top: 0, left: 0, width: 0, height: 0};
582+
} else {
583+
// This returns the boundary's coordinate with respect to the container. This case captures cases such as when you provide a custom boundary
584+
// like in ScrollingBoundaryContainerExample in Popover.stories.
585+
containerOffsetWithBoundary = getPosition(boundaryElement, container, false);
586+
}
565587

566588
let isContainerDescendentOfBoundary = nodeContains(boundaryElement, container);
567589
return calculatePositionInternal(

packages/@react-spectrum/menu/src/ContextualHelpTrigger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {FocusScope} from '@react-aria/focus';
1616
import {getInteractionModality} from '@react-aria/interactions';
1717
import helpStyles from '@adobe/spectrum-css-temp/components/contextualhelp/vars.css';
1818
import {nodeContains} from '@react-aria/utils';
19-
import {Popover} from './Popover';
19+
import {Popover} from '@react-spectrum/overlays';
2020
import React, {JSX, KeyboardEventHandler, ReactElement, useEffect, useRef, useState} from 'react';
2121
import ReactDOM from 'react-dom';
2222
import styles from '@adobe/spectrum-css-temp/components/menu/vars.css';

packages/@react-spectrum/menu/src/Popover.tsx

Lines changed: 0 additions & 240 deletions
This file was deleted.

packages/@react-spectrum/menu/src/SubmenuTrigger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {classNames, useIsMobileDevice} from '@react-spectrum/utils';
1414
import {Key} from '@react-types/shared';
1515
import {MenuContext, SubmenuTriggerContext, useMenuStateContext} from './context';
1616
import {mergeProps, nodeContains} from '@react-aria/utils';
17-
import {Popover} from './Popover';
17+
import {Popover} from '@react-spectrum/overlays';
1818
import React, {type JSX, ReactElement, useRef} from 'react';
1919
import ReactDOM from 'react-dom';
2020
import styles from '@adobe/spectrum-css-temp/components/menu/vars.css';

packages/@react-spectrum/menu/src/Underlay.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)