Skip to content

Commit bc47252

Browse files
committed
Merge branch 'main' into fix-all-document-active-element
# Conflicts: # eslint.config.mjs # packages/@react-aria/calendar/src/useRangeCalendar.ts # packages/@react-aria/dialog/src/useDialog.ts # packages/@react-aria/grid/src/useGridCell.ts # packages/@react-aria/gridlist/src/useGridListItem.ts # packages/@react-aria/menu/src/useSubmenuTrigger.ts # packages/@react-aria/overlays/src/useOverlayPosition.ts # packages/@react-aria/selection/src/useSelectableCollection.ts # packages/@react-spectrum/menu/src/ContextualHelpTrigger.tsx # packages/@react-spectrum/menu/src/SubmenuTrigger.tsx # packages/@react-spectrum/s2/src/TableView.tsx # packages/@react-spectrum/table/src/TableViewBase.tsx # packages/dev/eslint-plugin-rsp-rules/index.js # packages/react-aria-components/src/Popover.tsx
2 parents 44f94cf + 2e8c961 commit bc47252

File tree

20 files changed

+294
-34
lines changed

20 files changed

+294
-34
lines changed

eslint.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ export default [{
251251
"rsp-rules/sort-imports": [ERROR],
252252
"rsp-rules/no-non-shadow-contains": [ERROR],
253253
"rsp-rules/shadow-safe-active-element": [ERROR],
254+
"rsp-rules/faster-node-contains": [ERROR],
254255
"rulesdir/imports": [ERROR],
255256
"rulesdir/useLayoutEffectRule": [ERROR],
256257
"rulesdir/pure-render": [ERROR],
@@ -432,6 +433,7 @@ export default [{
432433
"rsp-rules/no-getByRole-toThrow": ERROR,
433434
"rsp-rules/no-non-shadow-contains": OFF,
434435
"rsp-rules/shadow-safe-active-element": OFF,
436+
"rsp-rules/faster-node-contains": OFF,
435437
"rulesdir/imports": OFF,
436438
"monorepo/no-internal-import": OFF,
437439
"jsdoc/require-jsdoc": OFF
@@ -510,6 +512,7 @@ export default [{
510512
],
511513

512514
rules: {
515+
"rsp-rules/faster-node-contains": OFF,
513516
"rsp-rules/no-non-shadow-contains": OFF,
514517
"rsp-rules/shadow-safe-active-element": OFF,
515518
},

packages/@react-aria/calendar/src/useRangeCalendar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaRangeCalendarProps, DateValue} from '@react-types/calendar';
1414
import {CalendarAria, useCalendarBase} from './useCalendarBase';
1515
import {FocusableElement, RefObject} from '@react-types/shared';
16-
import {getActiveElement, nodeContains, useEvent} from '@react-aria/utils';
16+
import {isFocusWithin, nodeContains, useEvent} from '@react-aria/utils';
1717
import {RangeCalendarState} from '@react-stately/calendar';
1818
import {useRef} from 'react';
1919

@@ -52,7 +52,7 @@ export function useRangeCalendar<T extends DateValue>(props: AriaRangeCalendarPr
5252
let target = e.target as Element;
5353
if (
5454
ref.current &&
55-
nodeContains(ref.current, getActiveElement()) &&
55+
isFocusWithin(ref.current) &&
5656
(!nodeContains(ref.current, target) || !target.closest('button, [role="button"]'))
5757
) {
5858
state.selectFocusedDate();

packages/@react-aria/dialog/src/useDialog.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {AriaDialogProps} from '@react-types/dialog';
1414
import {DOMAttributes, FocusableElement, RefObject} from '@react-types/shared';
15-
import {filterDOMProps, getActiveElement, nodeContains, useSlotId} from '@react-aria/utils';
15+
import {filterDOMProps, getActiveElement, isFocusWithin, useSlotId} from '@react-aria/utils';
1616
import {focusSafely} from '@react-aria/interactions';
1717
import {useEffect, useRef} from 'react';
1818
import {useOverlayFocusContain} from '@react-aria/overlays';
@@ -40,7 +40,7 @@ export function useDialog(props: AriaDialogProps, ref: RefObject<FocusableElemen
4040

4141
// Focus the dialog itself on mount, unless a child element is already focused.
4242
useEffect(() => {
43-
if (ref.current && !nodeContains(ref.current, getActiveElement())) {
43+
if (ref.current && !isFocusWithin(ref.current)) {
4444
focusSafely(ref.current);
4545

4646
// Safari on iOS does not move the VoiceOver cursor to the dialog

packages/@react-aria/grid/src/useGridCell.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {DOMAttributes, FocusableElement, Key, RefObject} from '@react-types/shared';
1414
import {focusSafely, isFocusVisible} from '@react-aria/interactions';
15-
import {getActiveElement, getScrollParent, mergeProps, nodeContains, scrollIntoViewport} from '@react-aria/utils';
15+
import {getActiveElement, getScrollParent, isFocusWithin, mergeProps, nodeContains, scrollIntoViewport} from '@react-aria/utils';
1616
import {getFocusableTreeWalker} from '@react-aria/focus';
1717
import {GridCollection, GridNode} from '@react-types/grid';
1818
import {gridMap} from './utils';
@@ -75,7 +75,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
7575
let treeWalker = getFocusableTreeWalker(ref.current);
7676
if (focusMode === 'child') {
7777
// If focus is already on a focusable child within the cell, early return so we don't shift focus
78-
if (nodeContains(ref.current, getActiveElement()) && ref.current !== getActiveElement()) {
78+
if (isFocusWithin(ref.current) && ref.current !== getActiveElement()) {
7979
return;
8080
}
8181

@@ -90,7 +90,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
9090

9191
if (
9292
(keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
93-
!nodeContains(ref.current, getActiveElement())
93+
!isFocusWithin(ref.current)
9494
) {
9595
focusSafely(ref.current);
9696
}

packages/@react-aria/gridlist/src/useGridListItem.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {chain, getActiveElement, getScrollParent, mergeProps, nodeContains, scrollIntoViewport, useSlotId, useSyntheticLinkProps} from '@react-aria/utils';
13+
import {chain, getActiveElement, getScrollParent, isFocusWithin, mergeProps, nodeContains, scrollIntoViewport, useSlotId, useSyntheticLinkProps} from '@react-aria/utils';
1414
import {DOMAttributes, FocusableElement, Key, RefObject, Node as RSNode} from '@react-types/shared';
1515
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
1616
import {getRowId, listMap} from './utils';
@@ -79,7 +79,7 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
7979
if (
8080
ref.current !== null &&
8181
((keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
82-
!nodeContains(ref.current, getActiveElement()))
82+
!isFocusWithin(ref.current))
8383
) {
8484
focusSafely(ref.current);
8585
}

packages/@react-aria/landmark/src/useLandmark.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ class LandmarkManager implements LandmarkManagerApi {
325325

326326
private focusMain() {
327327
let main = this.getLandmarkByRole('main');
328-
if (main && main.ref.current && nodeContains(document, main.ref.current)) {
328+
if (main && main.ref.current && main.ref.current.isConnected) {
329329
this.focusLandmark(main.ref.current, 'forward');
330330
return true;
331331
}
@@ -352,7 +352,7 @@ class LandmarkManager implements LandmarkManagerApi {
352352
}
353353

354354
// Otherwise, focus the landmark itself
355-
if (nextLandmark.ref.current && nodeContains(document, nextLandmark.ref.current)) {
355+
if (nextLandmark.ref.current && nextLandmark.ref.current.isConnected) {
356356
this.focusLandmark(nextLandmark.ref.current, backward ? 'backward' : 'forward');
357357
return true;
358358
}

packages/@react-aria/menu/src/useSubmenuTrigger.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {AriaMenuItemProps} from './useMenuItem';
1414
import {AriaMenuOptions} from './useMenu';
1515
import type {AriaPopoverProps, OverlayProps} from '@react-aria/overlays';
1616
import {FocusableElement, FocusStrategy, KeyboardEvent, Node, PressEvent, RefObject} from '@react-types/shared';
17-
import {focusWithoutScrolling, getActiveElement, nodeContains, useEvent, useId, useLayoutEffect} from '@react-aria/utils';
17+
import {focusWithoutScrolling, getActiveElement, isFocusWithin, nodeContains, useEvent, useId, useLayoutEffect} from '@react-aria/utils';
1818
import type {SubmenuTriggerState} from '@react-stately/menu';
1919
import {useCallback, useRef} from 'react';
2020
import {useLocale} from '@react-aria/i18n';
@@ -100,7 +100,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
100100
let submenuKeyDown = (e: KeyboardEvent) => {
101101
// If focus is not within the menu, assume virtual focus is being used.
102102
// This means some other input element is also within the popover, so we shouldn't close the menu.
103-
if (!nodeContains(e.currentTarget, getActiveElement())) {
103+
if (!isFocusWithin(e.currentTarget)) {
104104
return;
105105
}
106106

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {calculatePosition, getRect, PositionResult} from './calculatePosition';
1414
import {DOMAttributes, RefObject} from '@react-types/shared';
15-
import {getActiveElement, nodeContains, useLayoutEffect, useResizeObserver} from '@react-aria/utils';
15+
import {getActiveElement, isFocusWithin, useLayoutEffect, useResizeObserver} from '@react-aria/utils';
1616
import {Placement, PlacementAxis, PositionProps} from '@react-types/overlays';
1717
import {useCallback, useEffect, useRef, useState} from 'react';
1818
import {useCloseOnScroll} from './useCloseOnScroll';
@@ -154,7 +154,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
154154
// so it can be restored after repositioning. This way if the overlay height
155155
// changes, the focused element appears to stay in the same position.
156156
let anchor: ScrollAnchor | null = null;
157-
if (scrollRef.current && nodeContains(scrollRef.current, getActiveElement())) {
157+
if (scrollRef.current && isFocusWithin(scrollRef.current)) {
158158
let anchorRect = getActiveElement()?.getBoundingClientRect();
159159
let scrollRect = scrollRef.current.getBoundingClientRect();
160160
// Anchor from the top if the offset is in the top half of the scrollable element,

packages/@react-aria/selection/src/useSelectableCollection.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, focusWithoutScrolling, getActiveElement, isCtrlKeyPressed, isTabbable, mergeProps, nodeContains, scrollIntoView, scrollIntoViewport, useEvent, useRouter, useUpdateLayoutEffect} from '@react-aria/utils';
13+
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, focusWithoutScrolling, getActiveElement, isCtrlKeyPressed, isFocusWithin, isTabbable, mergeProps, nodeContains, scrollIntoView, scrollIntoViewport, useEvent, useRouter, useUpdateLayoutEffect} from '@react-aria/utils';
1414
import {dispatchVirtualFocus, getFocusableTreeWalker, moveVirtualFocus} from '@react-aria/focus';
1515
import {DOMAttributes, FocusableElement, FocusStrategy, Key, KeyboardDelegate, RefObject} from '@react-types/shared';
1616
import {flushSync} from 'react-dom';
@@ -315,7 +315,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
315315
// the containing element. We need to special case this so that tab will move focus out of the grid instead of looping between
316316
// focusing the containing cell and back to the non-tabbable child element
317317
let activeElement = getActiveElement();
318-
if (next && (!nodeContains(next, activeElement) || (activeElement && !isTabbable(activeElement)))) {
318+
if (next && (!isFocusWithin(next) || (activeElement && !isTabbable(activeElement)))) {
319319
focusWithoutScrolling(next);
320320
}
321321
}
@@ -380,7 +380,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
380380
let element = getItemElement(ref, manager.focusedKey);
381381
if (element instanceof HTMLElement) {
382382
// This prevents a flash of focus on the first/last element in the collection, or the collection itself.
383-
if (!nodeContains(element, getActiveElement()) && !shouldUseVirtualFocus) {
383+
if (!isFocusWithin(element) && !shouldUseVirtualFocus) {
384384
focusWithoutScrolling(element);
385385
}
386386

packages/@react-aria/utils/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
export {useId, mergeIds, useSlotId} from './useId';
1313
export {chain} from './chain';
1414
export {createShadowTreeWalker, ShadowTreeWalker} from './shadowdom/ShadowTreeWalker';
15-
export {getActiveElement, getEventTarget, nodeContains} from './shadowdom/DOMFunctions';
15+
export {getActiveElement, getEventTarget, nodeContains, isFocusWithin} from './shadowdom/DOMFunctions';
1616
export {getOwnerDocument, getOwnerWindow, isShadowRoot} from './domHelpers';
1717
export {mergeProps} from './mergeProps';
1818
export {mergeRefs} from './mergeRefs';

0 commit comments

Comments
 (0)