Skip to content

Commit 51a3116

Browse files
🐛 - fix: fix issues regarding form state
1 parent 77eef37 commit 51a3116

File tree

1 file changed

+51
-86
lines changed

1 file changed

+51
-86
lines changed

src/components/form/select/select.tsx

Lines changed: 51 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
useTypeahead,
1616
} from "@floating-ui/react";
1717
import clsx from "clsx";
18-
import React, { useEffect } from "react";
18+
import React, { MouseEventHandler, useEffect } from "react";
1919

2020
import { gettextFirst, ucFirst } from "../../../lib";
2121
import { Button } from "../../button";
@@ -167,59 +167,54 @@ export const Select: React.FC<SelectProps> = ({
167167

168168
/**
169169
* Handles a change of the selected (option) index.
170-
* @param event
170+
* @param _
171171
* @param index
172172
*/
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);
199182
}
200183

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.
209187
setIsOpen(false);
210188
}
211189

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+
*/
216204
const fakeInput = fakeInputRef.current as HTMLSelectElement;
217205
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,
222212
);
213+
fakeInput.dispatchEvent(changeEvent);
214+
onChange &&
215+
onChange(
216+
changeEvent as unknown as React.ChangeEvent<HTMLSelectElement>,
217+
);
223218
});
224219
};
225220

@@ -228,7 +223,7 @@ export const Select: React.FC<SelectProps> = ({
228223
: selectedIndices[0] !== undefined && selectedIndices[0] !== null
229224
? options[selectedIndices[0]].value
230225
: "";
231-
226+
const { onMouseDown, ...referenceProps } = getReferenceProps();
232227
return (
233228
<>
234229
<div
@@ -250,7 +245,15 @@ export const Select: React.FC<SelectProps> = ({
250245
aria-hidden={hidden}
251246
aria-disabled={disabled}
252247
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+
}}
254257
{...props}
255258
>
256259
{/* 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> = ({
289292
variant="transparent"
290293
className="mykn-select__pill-remove"
291294
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)}
312296
>
313297
<Outline.XMarkIcon />
314298
</Button>
@@ -326,26 +310,7 @@ export const Select: React.FC<SelectProps> = ({
326310
className="mykn-select__clear"
327311
type="button"
328312
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)}
349314
>
350315
<Outline.XCircleIcon />
351316
</button>

0 commit comments

Comments
 (0)