10
10
* governing permissions and limitations under the License.
11
11
*/
12
12
13
- import { FocusableElement , RefObject } from '@react-types/shared' ;
13
+ import { FocusableElement , Key , RefObject } from '@react-types/shared' ;
14
14
import React , { InputHTMLAttributes , JSX , ReactNode , useCallback , useRef } from 'react' ;
15
15
import { selectData } from './useSelect' ;
16
+ import { SelectionMode } from '@react-types/select' ;
16
17
import { SelectState } from '@react-stately/select' ;
17
18
import { useFormReset } from '@react-aria/utils' ;
18
19
import { useFormValidation } from '@react-aria/form' ;
@@ -41,9 +42,9 @@ export interface AriaHiddenSelectProps {
41
42
isDisabled ?: boolean
42
43
}
43
44
44
- export interface HiddenSelectProps < T > extends AriaHiddenSelectProps {
45
+ export interface HiddenSelectProps < T , M extends SelectionMode = 'single' > extends AriaHiddenSelectProps {
45
46
/** State for the select. */
46
- state : SelectState < T > ,
47
+ state : SelectState < T , M > ,
47
48
48
49
/** A ref to the trigger element. */
49
50
triggerRef : RefObject < FocusableElement | null >
@@ -70,7 +71,7 @@ export interface HiddenSelectAria {
70
71
* can be used in combination with `useSelect` to support browser form autofill, mobile form
71
72
* navigation, and native HTML form submission.
72
73
*/
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 {
74
75
let data = selectData . get ( state ) || { } ;
75
76
let { autoComplete, name = data . name , form = data . form , isDisabled = data . isDisabled } = props ;
76
77
let { validationBehavior, isRequired} = data ;
@@ -83,14 +84,23 @@ export function useHiddenSelect<T>(props: AriaHiddenSelectOptions, state: Select
83
84
}
84
85
} ) ;
85
86
86
- useFormReset ( props . selectRef , state . defaultSelectedKey , state . setSelectedKey ) ;
87
+ useFormReset ( props . selectRef , state . defaultValue , state . setValue ) ;
87
88
useFormValidation ( {
88
89
validationBehavior,
89
90
focus : ( ) => triggerRef . current ?. focus ( )
90
91
} , state , props . selectRef ) ;
91
92
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 ] ) ;
94
104
95
105
// In Safari, the <select> cannot have `display: none` or `hidden` for autofill to work.
96
106
// 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
114
124
tabIndex : - 1 ,
115
125
autoComplete,
116
126
disabled : isDisabled ,
127
+ multiple : state . selectionManager . selectionMode === 'multiple' ,
117
128
required : validationBehavior === 'native' && isRequired ,
118
129
name,
119
130
form,
120
- value : state . selectedKey ?? '' ,
131
+ value : ( state . value as string | string [ ] ) ?? '' ,
121
132
onChange,
122
133
onInput : onChange
123
134
}
@@ -128,7 +139,7 @@ export function useHiddenSelect<T>(props: AriaHiddenSelectOptions, state: Select
128
139
* Renders a hidden native `<select>` element, which can be used to support browser
129
140
* form autofill, mobile form navigation, and native form submission.
130
141
*/
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 {
132
143
let { state, triggerRef, label, name, form, isDisabled} = props ;
133
144
let selectRef = useRef ( null ) ;
134
145
let inputRef = useRef ( null ) ;
@@ -164,32 +175,43 @@ export function HiddenSelect<T>(props: HiddenSelectProps<T>): JSX.Element | null
164
175
let data = selectData . get ( state ) || { } ;
165
176
let { validationBehavior} = data ;
166
177
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
+ }
175
208
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.
179
209
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 } />
187
211
) ;
188
- }
212
+ } ) ;
189
213
190
- return (
191
- < input { ...inputProps } ref = { inputRef } />
192
- ) ;
214
+ return < > { res } </ > ;
193
215
}
194
216
195
217
return null ;
0 commit comments