Skip to content
Closed
Show file tree
Hide file tree
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: 35 additions & 4 deletions packages/react-aria/src/selection/useSelectableCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,11 @@ export function useSelectableCollection(
) {
navigateToKey(manager.lastSelectedKey ?? delegate.getLastKey?.());
} else {
navigateToKey(manager.firstSelectedKey ?? delegate.getFirstKey?.());
let firstKey = manager.firstSelectedKey ?? delegate.getFirstKey?.();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something is wrong the tests, they pass without this change as well

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this block was redundant, so I have removed it and reverted this section.

if (firstKey != null && manager.disabledKeys.has(firstKey)) {
firstKey = delegate.getFirstKey?.() ?? null;
}
navigateToKey(firstKey);
}
} else if (scrollRef.current) {
// Restore the scroll position to what it was before.
Expand Down Expand Up @@ -597,15 +601,13 @@ export function useSelectableCollection(
if (autoFocusRef.current) {
let focusedKey: Key | null = null;

// Check focus strategy to determine which item to focus
if (autoFocus === 'first') {
focusedKey = delegate.getFirstKey?.() ?? null;
}
if (autoFocus === 'last') {
focusedKey = delegate.getLastKey?.() ?? null;
}

// If there are any selected keys, make the first one the new focus target
let selectedKeys = manager.selectedKeys;
if (selectedKeys.size) {
for (let key of selectedKeys) {
Expand All @@ -616,6 +618,36 @@ export function useSelectableCollection(
}
}

let originalStrategyKey = focusedKey;
let visitedKeys = new Set<Key>();
let foundEnabledKey = false;

while (focusedKey != null && !manager.canSelectItem(focusedKey)) {
if (visitedKeys.has(focusedKey)) {
break;
}
visitedKeys.add(focusedKey);

let nextKey = delegate.getKeyBelow?.(focusedKey) ?? null;

if (nextKey == null || nextKey === focusedKey) {
break;
}

if (manager.canSelectItem(nextKey)) {
focusedKey = nextKey;
foundEnabledKey = true;
break;
}

focusedKey = nextKey;
}

// If no enabled key was found, restore original key (this may happen if all items are disabled)
if (!foundEnabledKey && focusedKey != null && !manager.canSelectItem(focusedKey)) {
focusedKey = originalStrategyKey;
}

manager.setFocused(true);
manager.setFocusedKey(focusedKey);

Expand All @@ -624,7 +656,6 @@ export function useSelectableCollection(
focusSafely(ref.current);
}

// Wait until the collection has items to autofocus.
if (manager.collection.size > 0) {
autoFocusRef.current = false;
didAutoFocusRef.current = true;
Expand Down
17 changes: 17 additions & 0 deletions packages/react-aria/test/selection/useSelectableCollection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,21 @@ describe('useSelectableCollection', () => {
expect(onSelectionChange2).not.toHaveBeenCalled();
});
});

describe('with disabled items boundary keys', () => {
it('should automatically focus the first available enabled item on autoFocus mount when leading items are disabled', () => {
let {getAllByRole} = render(
<List selectionMode="single" autoFocus="first" disabledKeys={['i1']}>
<Item key="i1">Disabled Absolute First Item</Item>
<Item key="i2">First Enabled Item</Item>
<Item key="i3">Second Enabled Item</Item>
</List>
);

let options = getAllByRole('option');

expect(document.activeElement).toBe(options[1]);
expect(options[1].textContent).toBe('First Enabled Item');
});
});
});