1010 * governing permissions and limitations under the License.
1111 */
1212
13- import { FocusableElement , RefObject } from '@react-types/shared' ;
13+ import { FocusableElement , Key , RefObject } from '@react-types/shared' ;
1414import React , { InputHTMLAttributes , JSX , ReactNode , useCallback , useRef } from 'react' ;
1515import { selectData } from './useSelect' ;
16+ import { SelectionMode } from '@react-types/select' ;
1617import { SelectState } from '@react-stately/select' ;
1718import { useFormReset } from '@react-aria/utils' ;
1819import { useFormValidation } from '@react-aria/form' ;
@@ -41,9 +42,9 @@ export interface AriaHiddenSelectProps {
4142 isDisabled ?: boolean
4243}
4344
44- export interface HiddenSelectProps < T > extends AriaHiddenSelectProps {
45+ export interface HiddenSelectProps < T , M extends SelectionMode = 'single' > extends AriaHiddenSelectProps {
4546 /** State for the select. */
46- state : SelectState < T > ,
47+ state : SelectState < T , M > ,
4748
4849 /** A ref to the trigger element. */
4950 triggerRef : RefObject < FocusableElement | null >
@@ -70,7 +71,7 @@ export interface HiddenSelectAria {
7071 * can be used in combination with `useSelect` to support browser form autofill, mobile form
7172 * navigation, and native HTML form submission.
7273 */
73- export function useHiddenSelect < T > ( props : AriaHiddenSelectOptions , state : SelectState < T > , triggerRef : RefObject < FocusableElement | null > ) : HiddenSelectAria {
74+ export function useHiddenSelect < T , M extends SelectionMode = 'single' > ( props : AriaHiddenSelectOptions , state : SelectState < T , M > , triggerRef : RefObject < FocusableElement | null > ) : HiddenSelectAria {
7475 let data = selectData . get ( state ) || { } ;
7576 let { autoComplete, name = data . name , form = data . form , isDisabled = data . isDisabled } = props ;
7677 let { validationBehavior, isRequired} = data ;
@@ -83,14 +84,23 @@ export function useHiddenSelect<T>(props: AriaHiddenSelectOptions, state: Select
8384 }
8485 } ) ;
8586
86- useFormReset ( props . selectRef , state . defaultSelectedKey , state . setSelectedKey ) ;
87+ useFormReset ( props . selectRef , state . defaultValue , state . setValue ) ;
8788 useFormValidation ( {
8889 validationBehavior,
8990 focus : ( ) => triggerRef . current ?. focus ( )
9091 } , state , props . selectRef ) ;
9192
92- // eslint-disable-next-line react-hooks/exhaustive-deps
93- let onChange = useCallback ( ( e : React . ChangeEvent < HTMLSelectElement > | React . FormEvent < HTMLSelectElement > ) => state . setSelectedKey ( e . currentTarget . value ) , [ state . setSelectedKey ] ) ;
93+ let setValue = state . setValue ;
94+ let onChange = useCallback ( ( e : React . ChangeEvent < HTMLSelectElement > ) => {
95+ if ( e . target . multiple ) {
96+ setValue ( Array . from (
97+ e . target . selectedOptions ,
98+ ( option ) => option . value
99+ ) as any ) ;
100+ } else {
101+ setValue ( e . currentTarget . value as any ) ;
102+ }
103+ } , [ setValue ] ) ;
94104
95105 // In Safari, the <select> cannot have `display: none` or `hidden` for autofill to work.
96106 // In Firefox, there must be a <label> to identify the <select> whereas other browsers
@@ -114,10 +124,11 @@ export function useHiddenSelect<T>(props: AriaHiddenSelectOptions, state: Select
114124 tabIndex : - 1 ,
115125 autoComplete,
116126 disabled : isDisabled ,
127+ multiple : state . selectionManager . selectionMode === 'multiple' ,
117128 required : validationBehavior === 'native' && isRequired ,
118129 name,
119130 form,
120- value : state . selectedKey ?? '' ,
131+ value : ( state . value as string | string [ ] ) ?? '' ,
121132 onChange,
122133 onInput : onChange
123134 }
@@ -128,7 +139,7 @@ export function useHiddenSelect<T>(props: AriaHiddenSelectOptions, state: Select
128139 * Renders a hidden native `<select>` element, which can be used to support browser
129140 * form autofill, mobile form navigation, and native form submission.
130141 */
131- export function HiddenSelect < T > ( props : HiddenSelectProps < T > ) : JSX . Element | null {
142+ export function HiddenSelect < T , M extends SelectionMode = 'single' > ( props : HiddenSelectProps < T , M > ) : JSX . Element | null {
132143 let { state, triggerRef, label, name, form, isDisabled} = props ;
133144 let selectRef = useRef ( null ) ;
134145 let inputRef = useRef ( null ) ;
@@ -164,32 +175,43 @@ export function HiddenSelect<T>(props: HiddenSelectProps<T>): JSX.Element | null
164175 let data = selectData . get ( state ) || { } ;
165176 let { validationBehavior} = data ;
166177
167- let inputProps : InputHTMLAttributes < HTMLInputElement > = {
168- type : 'hidden' ,
169- autoComplete : selectProps . autoComplete ,
170- name,
171- form,
172- disabled : isDisabled ,
173- value : state . selectedKey ?? ''
174- } ;
178+ // Always render at least one hidden input to ensure required form submission.
179+ let values : ( Key | null ) [ ] = Array . isArray ( state . value ) ? state . value : [ state . value ] ;
180+ if ( values . length === 0 ) {
181+ values = [ null ] ;
182+ }
183+
184+ let res = values . map ( ( value , i ) => {
185+ let inputProps : InputHTMLAttributes < HTMLInputElement > = {
186+ type : 'hidden' ,
187+ autoComplete : selectProps . autoComplete ,
188+ name,
189+ form,
190+ disabled : isDisabled ,
191+ value : value ?? ''
192+ } ;
193+
194+ if ( validationBehavior === 'native' ) {
195+ // Use a hidden <input type="text"> rather than <input type="hidden">
196+ // so that an empty value blocks HTML form submission when the field is required.
197+ return (
198+ < input
199+ key = { i }
200+ { ...inputProps }
201+ ref = { i === 0 ? inputRef : null }
202+ style = { { display : 'none' } }
203+ type = "text"
204+ required = { i === 0 ? selectProps . required : false }
205+ onChange = { ( ) => { /** Ignore react warning. */ } } />
206+ ) ;
207+ }
175208
176- if ( validationBehavior === 'native' ) {
177- // Use a hidden <input type="text"> rather than <input type="hidden">
178- // so that an empty value blocks HTML form submission when the field is required.
179209 return (
180- < input
181- { ...inputProps }
182- ref = { inputRef }
183- style = { { display : 'none' } }
184- type = "text"
185- required = { selectProps . required }
186- onChange = { ( ) => { /** Ignore react warning. */ } } />
210+ < input key = { i } { ...inputProps } ref = { i === 0 ? inputRef : null } />
187211 ) ;
188- }
212+ } ) ;
189213
190- return (
191- < input { ...inputProps } ref = { inputRef } />
192- ) ;
214+ return < > { res } </ > ;
193215 }
194216
195217 return null ;
0 commit comments