@@ -15,7 +15,7 @@ import {
15
15
useTypeahead ,
16
16
} from "@floating-ui/react" ;
17
17
import clsx from "clsx" ;
18
- import React , { useEffect } from "react" ;
18
+ import React , { MouseEventHandler , useEffect } from "react" ;
19
19
20
20
import { gettextFirst , ucFirst } from "../../../lib" ;
21
21
import { Button } from "../../button" ;
@@ -167,59 +167,54 @@ export const Select: React.FC<SelectProps> = ({
167
167
168
168
/**
169
169
* Handles a change of the selected (option) index.
170
- * @param event
170
+ * @param _
171
171
* @param index
172
172
*/
173
- const handleChange = ( event : React . UIEvent , index : number | null ) => {
174
- if ( index === null ) {
175
- setSelectedIndices ( [ ] ) ;
176
- setIsOpen ( false ) ;
177
-
178
- /*
179
- * Dispatch change event.
180
- *
181
- * A custom "change" event created with `detail` set to the selected option.
182
- * The event is dispatched on `fakeInputRef.current` setting `target` to a
183
- * native select (which in itself can be used to obtain the value without
184
- * the use of events).
185
- *
186
- * This aims to improve compatibility with various approaches of dealing
187
- * with forms.
188
- *
189
- * NOTE: Dispatching is delayed to allow React to `fakeInputRef.current`
190
- * before dispatching.
191
- */
192
- const fakeInput = fakeInputRef . current as HTMLSelectElement ;
193
- const changeEvent = eventFactory ( "change" , null , true , false , false ) ;
194
- fakeInput . dispatchEvent ( changeEvent ) ;
195
- onChange ?.(
196
- changeEvent as unknown as React . ChangeEvent < HTMLSelectElement > ,
197
- ) ;
198
- return ;
173
+ const handleChange = ( _ : React . UIEvent , index : number | null ) => {
174
+ const selectedOption = index !== null ? options [ index ] : null ;
175
+
176
+ // Multiple select, add/remove.
177
+ if ( multiple && index !== null ) {
178
+ const next = selectedIndices . includes ( index )
179
+ ? selectedIndices . filter ( ( i ) => i !== index ) // Remove
180
+ : [ ...selectedIndices , index ] ; // Add
181
+ setSelectedIndices ( next ) ;
199
182
}
200
183
201
- let next : number [ ] ;
202
- if ( multiple ) {
203
- next = selectedIndices . includes ( index )
204
- ? selectedIndices . filter ( ( i ) => i !== index )
205
- : [ ...selectedIndices , index ] ;
206
- setIsOpen ( true ) ;
207
- } else {
208
- next = [ index ] ;
184
+ // Single
185
+ else {
186
+ setSelectedIndices ( index !== null ? [ index ] : [ ] ) ; // To array.
209
187
setIsOpen ( false ) ;
210
188
}
211
189
212
- setSelectedIndices ( next ) ;
213
- const selectedOptions = next . map ( ( i ) => options [ i ] ) ;
214
- const detail = multiple ? selectedOptions : selectedOptions [ 0 ] || null ;
215
-
190
+ /*
191
+ * Dispatch change event.
192
+ *
193
+ * A custom "change" event created with `detail` set to the selected option.
194
+ * The event is dispatched on `fakeInputRef.current` setting `target` to a
195
+ * native select (which in itself can be used to obtain the value without
196
+ * the use of events).
197
+ *
198
+ * This aims to improve compatibility with various approaches of dealing
199
+ * with forms.
200
+ *
201
+ * NOTE: Dispatching is delayed to allow React to `fakeInputRef.current`
202
+ * before dispatching.
203
+ */
216
204
const fakeInput = fakeInputRef . current as HTMLSelectElement ;
217
205
setTimeout ( ( ) => {
218
- const changeEvent = eventFactory ( "change" , detail , true , false , false ) ;
219
- fakeInput . dispatchEvent ( changeEvent ) ;
220
- onChange ?.(
221
- changeEvent as unknown as React . ChangeEvent < HTMLSelectElement > ,
206
+ const changeEvent = eventFactory (
207
+ "change" ,
208
+ selectedOption ,
209
+ true ,
210
+ false ,
211
+ false ,
222
212
) ;
213
+ fakeInput . dispatchEvent ( changeEvent ) ;
214
+ onChange &&
215
+ onChange (
216
+ changeEvent as unknown as React . ChangeEvent < HTMLSelectElement > ,
217
+ ) ;
223
218
} ) ;
224
219
} ;
225
220
@@ -228,7 +223,7 @@ export const Select: React.FC<SelectProps> = ({
228
223
: selectedIndices [ 0 ] !== undefined && selectedIndices [ 0 ] !== null
229
224
? options [ selectedIndices [ 0 ] ] . value
230
225
: "" ;
231
-
226
+ const { onMouseDown , ... referenceProps } = getReferenceProps ( ) ;
232
227
return (
233
228
< >
234
229
< div
@@ -250,7 +245,15 @@ export const Select: React.FC<SelectProps> = ({
250
245
aria-hidden = { hidden }
251
246
aria-disabled = { disabled }
252
247
onBlur = { handleBlur }
253
- { ...getReferenceProps ( ) }
248
+ { ...referenceProps }
249
+ onMouseDown = { ( e ) => {
250
+ const target = e . target as HTMLElement ;
251
+ // Override floating ui behavior for remove/close buttons.
252
+ if ( target . classList . contains ( "mykn-icon" ) ) {
253
+ return ;
254
+ }
255
+ ( onMouseDown as MouseEventHandler ) ( e ) ;
256
+ } }
254
257
{ ...props }
255
258
>
256
259
{ /* This is here for compatibility with native forms, as well as a providing a target for change events. */ }
@@ -289,26 +292,7 @@ export const Select: React.FC<SelectProps> = ({
289
292
variant = "transparent"
290
293
className = "mykn-select__pill-remove"
291
294
aria-label = { `Remove ${ options [ i ] ?. label } ` }
292
- onClick = { ( e ) => {
293
- e . stopPropagation ( ) ; // prevent dropdown from toggling
294
- const next = selectedIndices . filter ( ( j ) => j !== i ) ;
295
- setSelectedIndices ( next ) ;
296
-
297
- const detail = next . map ( ( j ) => options [ j ] ) ;
298
-
299
- const fakeInput = fakeInputRef . current ! ;
300
- const changeEvent = eventFactory (
301
- "change" ,
302
- detail ,
303
- true ,
304
- false ,
305
- false ,
306
- ) ;
307
- fakeInput . dispatchEvent ( changeEvent ) ;
308
- onChange ?.(
309
- changeEvent as unknown as React . ChangeEvent < HTMLSelectElement > ,
310
- ) ;
311
- } }
295
+ onClick = { ( e ) => handleChange ( e , i ) }
312
296
>
313
297
< Outline . XMarkIcon />
314
298
</ Button >
@@ -326,26 +310,7 @@ export const Select: React.FC<SelectProps> = ({
326
310
className = "mykn-select__clear"
327
311
type = "button"
328
312
aria-label = { ucFirst ( _labelClear ) }
329
- onClick = { ( e ) => {
330
- if ( multiple ) {
331
- setSelectedIndices ( [ ] ) ;
332
- // fire a change with empty array detail
333
- const fake = fakeInputRef . current ! ;
334
- const changeEvent = eventFactory (
335
- "change" ,
336
- [ ] ,
337
- true ,
338
- false ,
339
- false ,
340
- ) ;
341
- fake . dispatchEvent ( changeEvent ) ;
342
- onChange ?.(
343
- changeEvent as unknown as React . ChangeEvent < HTMLSelectElement > ,
344
- ) ;
345
- } else {
346
- handleChange ( e , null ) ;
347
- }
348
- } }
313
+ onClick = { ( e ) => handleChange ( e , null ) }
349
314
>
350
315
< Outline . XCircleIcon />
351
316
</ button >
0 commit comments