diff --git a/packages/components/src/spectrum/comboBox/ComboBox.tsx b/packages/components/src/spectrum/comboBox/ComboBox.tsx index 98d36cdff..685ee7eb6 100644 --- a/packages/components/src/spectrum/comboBox/ComboBox.tsx +++ b/packages/components/src/spectrum/comboBox/ComboBox.tsx @@ -3,26 +3,32 @@ import { ComboBox as SpectrumComboBox, SpectrumComboBoxProps, } from '@adobe/react-spectrum'; -import type { FocusableRef } from '@react-types/shared'; +import type { DOMRef } from '@react-types/shared'; import cl from 'classnames'; import type { NormalizedItem } from '../utils'; import { PickerPropsT, usePickerProps } from '../picker'; +import useMultiRef from '../picker/useMultiRef'; export type ComboBoxProps = PickerPropsT>; export const ComboBox = React.forwardRef(function ComboBox( { UNSAFE_className, ...props }: ComboBoxProps, - ref: FocusableRef + ref: DOMRef ): JSX.Element { - const { defaultSelectedKey, disabledKeys, selectedKey, ...comboBoxProps } = - usePickerProps(props); - + const { + defaultSelectedKey, + disabledKeys, + selectedKey, + ref: scrollRef, + ...comboBoxProps + } = usePickerProps(props); + const pickerRef = useMultiRef(ref, scrollRef); return ( (props); - +export const Picker = React.forwardRef(function Picker( + { UNSAFE_className, ...props }: PickerProps, + ref: DOMRef +): JSX.Element { + const { + defaultSelectedKey, + disabledKeys, + selectedKey, + ref: scrollRef, + ...pickerProps + } = usePickerProps(props); + const pickerRef = useMultiRef(ref, scrollRef); return ( ); -} +}); export default Picker; diff --git a/packages/components/src/spectrum/picker/useMultiRef.test.ts b/packages/components/src/spectrum/picker/useMultiRef.test.ts new file mode 100644 index 000000000..7237f3c2d --- /dev/null +++ b/packages/components/src/spectrum/picker/useMultiRef.test.ts @@ -0,0 +1,59 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useMultiRef from './useMultiRef'; + +describe('useMultiRef', () => { + it('should assign the ref to all refs passed in', () => { + const ref1 = jest.fn(); + const ref2 = jest.fn(); + const ref3 = jest.fn(); + const { result } = renderHook(() => useMultiRef(ref1, ref2, ref3)); + const multiRef = result.current; + const element = document.createElement('div'); + multiRef(element); + expect(ref1).toHaveBeenCalledWith(element); + expect(ref2).toHaveBeenCalledWith(element); + expect(ref3).toHaveBeenCalledWith(element); + }); + + it('should assign the ref to all refs passed in with null', () => { + const ref1 = jest.fn(); + const ref2 = jest.fn(); + const ref3 = jest.fn(); + const { result } = renderHook(() => useMultiRef(ref1, ref2, ref3)); + const multiRef = result.current; + multiRef(null); + expect(ref1).toHaveBeenCalledWith(null); + expect(ref2).toHaveBeenCalledWith(null); + expect(ref3).toHaveBeenCalledWith(null); + }); + + it('should work with non-function refs', () => { + const ref1 = { current: null }; + const ref2 = { current: null }; + const ref3 = { current: null }; + const { result } = renderHook(() => + useMultiRef(ref1, ref2, ref3) + ); + const multiRef = result.current; + const element = document.createElement('div'); + multiRef(element); + expect(ref1.current).toBe(element); + expect(ref2.current).toBe(element); + expect(ref3.current).toBe(element); + }); + + it('should handle a mix of function and non-function refs', () => { + const ref1 = jest.fn(); + const ref2 = { current: null }; + const ref3 = jest.fn(); + const { result } = renderHook(() => + useMultiRef(ref1, ref2, ref3) + ); + const multiRef = result.current; + const element = document.createElement('div'); + multiRef(element); + expect(ref1).toHaveBeenCalledWith(element); + expect(ref2.current).toBe(element); + expect(ref3).toHaveBeenCalledWith(element); + }); +}); diff --git a/packages/components/src/spectrum/picker/useMultiRef.ts b/packages/components/src/spectrum/picker/useMultiRef.ts new file mode 100644 index 000000000..5bdafe0b3 --- /dev/null +++ b/packages/components/src/spectrum/picker/useMultiRef.ts @@ -0,0 +1,22 @@ +import { MutableRefObject, Ref, RefCallback, useCallback } from 'react'; + +/** + * Takes in multiple refs and then returns one ref that can be assigned to the component. + * In turn all the refs passed in will be assigned when the ref returned is assigned. + * @param refs The refs to assign + */ +function useMultiRef(...refs: readonly Ref[]): RefCallback { + return useCallback(newRef => { + refs.forEach(ref => { + if (typeof ref === 'function') { + ref(newRef); + } else if (ref != null) { + // eslint-disable-next-line no-param-reassign + (ref as MutableRefObject).current = newRef; + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, refs); +} + +export default useMultiRef;