Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions packages/@react-aria/overlays/src/useOverlayPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {useCallback, useEffect, useRef, useState} from 'react';
import {useCloseOnScroll} from './useCloseOnScroll';
import {useLayoutEffect, useResizeObserver} from '@react-aria/utils';
import {useLocale} from '@react-aria/i18n';
import {getVirtuallyFocusedElement} from "@react-aria/focus";

export interface AriaPositionProps extends PositionProps {
/**
Expand Down Expand Up @@ -153,19 +154,29 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
// This stores the offset of the anchor element from the scroll container
// so it can be restored after repositioning. This way if the overlay height
// changes, the focused element appears to stay in the same position.
let activeElement: Element | null = null;
let anchor: ScrollAnchor | null = null;
if (scrollRef.current && scrollRef.current.contains(document.activeElement)) {
let anchorRect = document.activeElement?.getBoundingClientRect();
let scrollRect = scrollRef.current.getBoundingClientRect();
// Anchor from the top if the offset is in the top half of the scrollable element,
// otherwise anchor from the bottom.
anchor = {
type: 'top',
offset: (anchorRect?.top ?? 0) - scrollRect.top
};
if (anchor.offset > scrollRect.height / 2) {
anchor.type = 'bottom';
anchor.offset = (anchorRect?.bottom ?? 0) - scrollRect.bottom;
if (scrollRef.current) {
if (scrollRef.current.contains(document.activeElement)) {
activeElement = document.activeElement;
}
let virtuallyFocusedElement = getVirtuallyFocusedElement(document);
if (scrollRef.current.contains(virtuallyFocusedElement)) {
activeElement = virtuallyFocusedElement;
}
if (activeElement) {
let anchorRect = activeElement.getBoundingClientRect();
let scrollRect = scrollRef.current.getBoundingClientRect();
// Anchor from the top if the offset is in the top half of the scrollable element,
// otherwise anchor from the bottom.
anchor = {
type: 'top',
offset: (anchorRect?.top ?? 0) - scrollRect.top
};
if (anchor.offset > scrollRect.height / 2) {
anchor.type = 'bottom';
anchor.offset = (anchorRect?.bottom ?? 0) - scrollRect.bottom;
}
}
}

Expand Down Expand Up @@ -208,8 +219,8 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
overlay.style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' : '';

// Restore scroll position relative to anchor element.
if (anchor && document.activeElement && scrollRef.current) {
let anchorRect = document.activeElement.getBoundingClientRect();
if (anchor && activeElement && scrollRef.current) {
let anchorRect = activeElement.getBoundingClientRect();
let scrollRect = scrollRef.current.getBoundingClientRect();
let newOffset = anchorRect[anchor.type] - scrollRect[anchor.type];
scrollRef.current.scrollTop += newOffset - anchor.offset;
Expand Down