Skip to content

Commit

Permalink
Do not render Select listbox children while it's closed
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Nov 4, 2024
1 parent 51505f0 commit 4c4c10c
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 16 deletions.
10 changes: 1 addition & 9 deletions src/components/input/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -545,14 +545,6 @@ function SelectMain<T>({
selector: '[role="option"]:not([aria-disabled="true"])',
});

useLayoutEffect(() => {
// Focus toggle button after closing listbox, only if previously focused
// element was inside the listbox itself
if (!listboxOpen && listboxRef.current!.contains(document.activeElement)) {
buttonRef.current!.focus();
}
}, [buttonRef, listboxOpen]);

return (
<div
className={classnames(
Expand Down Expand Up @@ -640,7 +632,7 @@ function SelectMain<T>({
popover={listboxAsPopover ? 'auto' : undefined}
onScroll={onListboxScroll}
>
{children}
{listboxOpen && children}
</ul>
</SelectContext.Provider>
</div>
Expand Down
42 changes: 35 additions & 7 deletions src/components/input/test/Select-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ describe('Select', () => {
const isListboxClosed = wrapper =>
getListbox(wrapper).prop('data-listbox-open') === false;

const openListbox = wrapper => {
if (isListboxClosed(wrapper)) {
toggleListbox(wrapper);
}
};

const listboxDidDropUp = wrapper => {
const { top: listboxTop } = getListbox(wrapper)
.getDOMNode()
Expand All @@ -112,10 +118,14 @@ describe('Select', () => {
return listboxTop < buttonTop;
};

const clickOption = (wrapper, id) =>
const clickOption = (wrapper, id) => {
openListbox(wrapper);
wrapper.find(`[data-testid="option-${id}"]`).simulate('click');
};

function clickOptionCheckbox(wrapper, id) {
openListbox(wrapper);

const checkbox = wrapper
.find(`[data-testid="option-${id}"]`)
.closest('[role="option"]')
Expand All @@ -130,14 +140,19 @@ describe('Select', () => {
checkbox.simulate('change');
}

const pressKeyInOption = (wrapper, id, key) =>
const pressKeyInOption = (wrapper, id, key) => {
openListbox(wrapper);

wrapper
.find(`[data-testid="option-${id}"]`)
.closest('[role="option"]')
.getDOMNode()
.dispatchEvent(new KeyboardEvent('keydown', { key }));
};

function pressKeyInOptionCheckbox(wrapper, id, key) {
openListbox(wrapper);

const checkbox = wrapper
.find(`[data-testid="option-${id}"]`)
.closest('[role="option"]')
Expand All @@ -152,10 +167,12 @@ describe('Select', () => {
checkbox.getDOMNode().dispatchEvent(new KeyboardEvent('keydown', { key }));
}

const isOptionSelected = (wrapper, id) =>
wrapper
const isOptionSelected = (wrapper, id) => {
openListbox(wrapper);
return wrapper
.find(`[data-testid="option-${id}"]`)
.exists('[data-testid="selected-option"]');
};

it('changes selected value when an option is clicked', () => {
const onChange = sinon.stub();
Expand All @@ -177,6 +194,7 @@ describe('Select', () => {
const clickDisabledOption = () =>
wrapper.find(`[data-testid="option-4"]`).simulate('click');

toggleListbox(wrapper);
clickDisabledOption();
assert.notCalled(onChange);
});
Expand Down Expand Up @@ -222,6 +240,8 @@ describe('Select', () => {
.find(`[data-testid="option-${id}"]`)
.exists('[data-testid="disabled-option"]');

openListbox(wrapper);

assert.isFalse(isOptionDisabled(1));
assert.isFalse(isOptionDisabled(2));
assert.isFalse(isOptionDisabled(3));
Expand Down Expand Up @@ -293,6 +313,10 @@ describe('Select', () => {

it('restores focus to toggle button after closing listbox', () => {
const wrapper = createComponent();
const toggleButtonDOMNode = getToggleButton(wrapper).getDOMNode();

// Focus toggle button before opening listbox
toggleButtonDOMNode.focus();
toggleListbox(wrapper);

// Focus listbox option before closing listbox
Expand All @@ -301,10 +325,14 @@ describe('Select', () => {
.getDOMNode()
.closest('[role="option"]')
.focus();

toggleListbox(wrapper);
wrapper.update();

assert.equal(document.activeElement, getToggleButton(wrapper).getDOMNode());
// After closing listbox, the focus should have returned to the toggle
// button, which was the last focused element before all children were
// removed
assert.equal(document.activeElement, toggleButtonDOMNode);
});

it('displays listbox when ArrowDown is pressed on toggle', () => {
Expand Down Expand Up @@ -582,7 +610,6 @@ describe('Select', () => {
{ Component: MultiSelect },
);

toggleListbox(wrapper);
clickOptionCheckbox(wrapper, 2);

// When a not-yet-selected item is clicked, it will be selected
Expand All @@ -606,7 +633,6 @@ describe('Select', () => {
{ Component: MultiSelect },
);

toggleListbox(wrapper);
clickOptionCheckbox(wrapper, 3);

// When an already selected item is clicked, it will be de-selected
Expand Down Expand Up @@ -669,6 +695,7 @@ describe('Select', () => {
{ value: [] },
{ Component: MultiSelect },
);
openListbox(wrapper);

// Spy on checkbox focus
const checkbox = wrapper
Expand All @@ -688,6 +715,7 @@ describe('Select', () => {
{ value: [] },
{ Component: MultiSelect },
);
openListbox(wrapper);

const option = wrapper
.find(`[data-testid="option-${optionId}"]`)
Expand Down

0 comments on commit 4c4c10c

Please sign in to comment.