Skip to content

Commit

Permalink
fix(select): Forward ref to Select input (#2892)
Browse files Browse the repository at this point in the history
Adds back ref forwarding to the `<Select>` component. The `ref` is the hidden server input with `focus` and `blur` methods redirecting to the visual input.

Fixes: #2887

[category:Components]

Co-authored-by: manuel.carrera <[email protected]>
  • Loading branch information
mannycarrera4 and manuel.carrera authored Aug 27, 2024
1 parent 291cce3 commit b511c7f
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 7 deletions.
17 changes: 17 additions & 0 deletions cypress/integration/Select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,23 @@ describe('Select', () => {
});
});

context.only(`given the "Ref Forwarding" story is rendered`, () => {
beforeEach(() => {
h.stories.load('Components/Inputs/Select', 'Ref Forwarding');
});

it('should not have any axe errors', () => {
cy.checkA11y();
});

context('the select input', () => {
it('should receive focus via ref forwarding when the button is clicked', () => {
cy.findByRole('button', {name: 'Focus Select'}).click();
cy.findByRole('combobox').should('have.focus');
});
});
});

context(`given the "Complex" story is rendered`, () => {
beforeEach(() => {
h.stories.load('Components/Inputs/Select', 'Complex');
Expand Down
17 changes: 10 additions & 7 deletions modules/react/select/lib/hooks/useSelectInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,27 @@ export const useSelectInput = composeHooks(
model.state.targetRef as any as React.Ref<HTMLInputElement>
);

const {localRef, elementRef} = useLocalRef(ref as React.Ref<HTMLInputElement>);
const {localRef: hiddenLocalRef, elementRef: hiddenElementRef} = useLocalRef(
ref as React.Ref<HTMLInputElement>
);

// We need to create a proxy between the multiple inputs. We need to redirect a few methods to
// the visible input
React.useImperativeHandle(
elementRef,
hiddenElementRef,
() => {
if (localRef.current) {
localRef.current.focus = (options?: FocusOptions) => {
if (hiddenLocalRef.current) {
hiddenLocalRef.current.focus = (options?: FocusOptions) => {
textInputRef.current!.focus(options);
};
localRef.current.blur = () => {
hiddenLocalRef.current.blur = () => {
textInputRef.current!.blur();
};
}

return localRef.current!;
return hiddenLocalRef.current!;
},
[textInputRef, localRef]
[textInputRef, hiddenLocalRef]
);

// Remap the Popup model's targetRef to be the visible ref. `ref` and `model.state.targetRef` are already linked. We have to override that.
Expand Down Expand Up @@ -172,6 +174,7 @@ export const useSelectInput = composeHooks(
},
onChange: handleOnChange,
autoComplete: 'off',
ref: hiddenElementRef,
// When the hidden input is focused, we want to show the focus/hover states of the input that sits below it.
onFocus() {
textInputRef.current?.focus();
Expand Down

0 comments on commit b511c7f

Please sign in to comment.